├── .gitignore ├── readme.md ├── source ├── .gitignore ├── App.config ├── Config.cs ├── Cypher.cs ├── HttpServer.cs ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.de.resx ├── MainForm.resx ├── MainForm.ru.resx ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── ScriptRepository.cs ├── Servlets.cs ├── Steamworks.cs ├── UpdateForm.Designer.cs ├── UpdateForm.cs ├── UpdateForm.resx ├── Win32.cs ├── WinService.cmd ├── WinService.cs ├── XWebClient.cs ├── create-extraQL-zip.cmd ├── extraQL.csproj ├── extraQL.csproj.DotSettings ├── extraQL.csproj.DotSettings.user ├── extraQL.ico ├── extraQL.sln ├── images │ ├── bottom.png │ ├── bottomleft.png │ ├── bottomright.png │ ├── left.png │ ├── maxsize.png │ ├── minsize.png │ ├── offline.jpg │ ├── right.png │ ├── top.png │ ├── topleft.png │ └── topright.png ├── resources │ ├── background.png │ ├── close.png │ ├── logo.png │ ├── minimize.png │ └── trayIcon.png ├── scripts │ ├── attic │ │ ├── altBrowser.usr.js │ │ ├── antiAjax.usr.js │ │ ├── autoExec.usr.js │ │ ├── autoInvite.usr.js │ │ ├── demoBrowser.usr.js │ │ ├── docker.usr.js │ │ ├── escaper.usr.js │ │ ├── esreality.usr.js │ │ ├── extGraphHist.usr.js │ │ ├── extStats.usr.js │ │ ├── extraQL.js │ │ ├── friendCommands.usr.js │ │ ├── gametype.usr.js │ │ ├── hook.js │ │ ├── irc.usr.js │ │ ├── joinGame.usr.js │ │ ├── keyboardNav.usr.js │ │ ├── linkify.usr.js │ │ ├── links.usr.js │ │ ├── matchtip.usr.js │ │ ├── messageBeep.usr.js │ │ ├── openChat.usr.js │ │ ├── playerStatus.usr.js │ │ ├── profileJumper.usr.js │ │ ├── qlRanks.usr.js │ │ ├── qlping.usr.js │ │ ├── raceTop10.usr.js │ │ ├── raceboard.usr.js │ │ ├── resizeLayout.usr.js │ │ ├── rosterGroup.usr.js │ │ ├── samPresets.usr.js │ │ ├── specHelper.usr.js │ │ ├── startpage.usr.js │ │ ├── streamNotifier.usr.js │ │ ├── toolTip.usr.js │ │ ├── twitch.usr.js │ │ ├── twitter.usr.js │ │ └── weightedAcc.usr.js │ ├── autoExec.js │ ├── instaBounce.js │ ├── postalDump.js │ ├── qlstats.js │ ├── quakeTv.js │ ├── steamNick.js │ └── whois.js ├── steam_api.dll └── translation.xlsx └── workshop ├── extraQL.vdf ├── extraQL1.png ├── extraQL2.png └── extraQL3.png /.gitignore: -------------------------------------------------------------------------------- 1 | source/_ReSharper*/ 2 | bin/ 3 | *.zip 4 | source/.vs/config/applicationhost.config 5 | source/scripts/_cycle.js 6 | Thumbs.db 7 | source/.vs/extraQL/v14/.suo 8 | source/extraQL.csproj.user 9 | workshop/content/ 10 | workshop/buildWorkshopItem.cmd 11 | /source/.vs/ 12 | -------------------------------------------------------------------------------- /source/.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | obj/* 3 | extraQL.sln.DotSettings 4 | extraQL.sln.DotSettings.user 5 | extraQL.v12.suo 6 | extraQL.zip 7 | images/Thumbs.db 8 | resources/Thumbs.db 9 | extraQL*.zip 10 | extraQL.ini 11 | resources/localhost.pvk 12 | -------------------------------------------------------------------------------- /source/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /source/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Windows.Forms; 6 | 7 | namespace ExtraQL 8 | { 9 | public class Config 10 | { 11 | private readonly Dictionary settings = new Dictionary(); 12 | private readonly Dictionary activeScripts = new Dictionary(); 13 | 14 | public readonly string AppBaseDir; 15 | 16 | #region ctor() 17 | public Config() 18 | { 19 | AppBaseDir = Path.GetDirectoryName(Application.ExecutablePath) ?? ""; 20 | if (AppBaseDir.ToLower().EndsWith("\\bin\\debug")) 21 | AppBaseDir = Path.GetDirectoryName(Path.GetDirectoryName(AppBaseDir)); 22 | } 23 | #endregion 24 | 25 | #region LoadSettings() 26 | public void LoadSettings() 27 | { 28 | this.settings.Clear(); 29 | this.settings["advanced"] = "0"; 30 | this.settings["systemTray"] = "0"; 31 | this.settings["startMinimized"] = "0"; 32 | this.settings["autostart"] = "0"; 33 | this.settings["log"] = "0"; 34 | this.settings["followLog"] = "0"; 35 | this.settings["logAllRequests"] = "0"; 36 | this.settings["autoquit"] = "0"; 37 | this.settings["quakelive_steam.exe"] = ""; 38 | this.settings["nickQuake"] = ""; 39 | this.settings["nickSteam"] = ""; 40 | this.settings["skipWorkshopNotice"] = "0"; 41 | this.settings["steamAppId"] = "349090"; 42 | this.settings["steamId"] = ""; 43 | this.settings["startServerBrowser"] = "0"; 44 | this.settings["closeServerBrowser"] = "0"; 45 | this.settings["webpakWorkshopItem"] = "0"; 46 | this.settings["locale"] = ""; 47 | this.settings["scripts"] = ""; 48 | this.settings["ignoreStaleWebPak"] = "0"; 49 | 50 | 51 | var configFile = this.ConfigFile; 52 | if (File.Exists(configFile)) 53 | { 54 | var lines = File.ReadAllLines(configFile); 55 | foreach (var line in lines) 56 | { 57 | var parts = line.Split(new[] { '=' }, 2); 58 | if (parts.Length < 2) continue; 59 | var key = parts[0].Trim(); 60 | var value = parts[1].Trim(); 61 | if (settings.ContainsKey(key)) // ignore unsupported settings 62 | settings[key] = value; 63 | } 64 | } 65 | 66 | try 67 | { 68 | foreach (var script in this.settings["scripts"].Split(',')) 69 | { 70 | var parts = script.Split(':'); 71 | if (parts.Length == 2) 72 | this.activeScripts[parts[0]] = int.Parse(parts[1].Trim()) != 0; 73 | } 74 | } 75 | catch 76 | { 77 | } 78 | } 79 | 80 | 81 | #endregion 82 | 83 | #region GetString(), GetBool() 84 | 85 | public string GetString(string setting) 86 | { 87 | return this.settings[setting]; 88 | } 89 | 90 | public bool GetBool(string setting) 91 | { 92 | return this.settings[setting] == "1"; 93 | } 94 | #endregion 95 | 96 | #region Set() 97 | public void Set(string setting, string value) 98 | { 99 | if (!settings.ContainsKey(setting)) 100 | throw new ArgumentOutOfRangeException(nameof(setting), value); 101 | this.settings[setting] = value; 102 | } 103 | 104 | public void Set(string setting, bool value) 105 | { 106 | this.Set(setting, value ? "1" : "0"); 107 | } 108 | #endregion 109 | 110 | #region SaveSettings() 111 | public void SaveSettings() 112 | { 113 | var sb = new StringBuilder(); 114 | foreach (var script in this.activeScripts) 115 | { 116 | if (sb.Length > 0) sb.Append(','); 117 | sb.Append(script.Key).Append(':').Append(script.Value ? "1" : "0"); 118 | } 119 | this.settings["scripts"] = sb.ToString(); 120 | 121 | StringBuilder config = new StringBuilder(); 122 | config.AppendLine("[extraQL]"); 123 | foreach (var entry in this.settings) 124 | config.AppendLine(entry.Key + "=" + entry.Value); 125 | File.WriteAllText(this.ConfigFile, config.ToString(), Encoding.UTF8); 126 | } 127 | #endregion 128 | 129 | #region ConfigFile 130 | private string ConfigFile => Path.Combine(this.AppBaseDir, "extraQL.ini"); 131 | 132 | #endregion 133 | 134 | #region GetScriptState(), SetScriptState() 135 | public bool? GetScriptState(string id) 136 | { 137 | bool val; 138 | return this.activeScripts.TryGetValue(id, out val) ? (bool?)val : null; 139 | } 140 | 141 | public void SetScriptState(string id, bool active) 142 | { 143 | this.activeScripts[id] = active; 144 | } 145 | #endregion 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /source/Cypher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace ExtraQL 6 | { 7 | internal class Cypher 8 | { 9 | private static readonly byte[] entropy = Encoding.UTF8.GetBytes("Some salt to spice up the password"); 10 | 11 | public static string EncryptString(string input) 12 | { 13 | byte[] encryptedData = ProtectedData.Protect( 14 | Encoding.UTF8.GetBytes(input), 15 | entropy, 16 | DataProtectionScope.CurrentUser); 17 | return Convert.ToBase64String(encryptedData); 18 | } 19 | 20 | public static string DecryptString(string encryptedData) 21 | { 22 | try 23 | { 24 | byte[] decryptedData = ProtectedData.Unprotect( 25 | Convert.FromBase64String(encryptedData), 26 | entropy, 27 | DataProtectionScope.CurrentUser); 28 | return Encoding.UTF8.GetString(decryptedData); 29 | } 30 | catch 31 | { 32 | return ""; 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /source/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace ExtraQL 5 | { 6 | static class Program 7 | { 8 | public const string WinServiceSwitch = "-service"; 9 | public const string BackgroundSwitch = "-background"; 10 | 11 | #region Main() 12 | [STAThread] 13 | static void Main() 14 | { 15 | AppDomain.CurrentDomain.UnhandledException += (sender, args) => HandleException(args.ExceptionObject as Exception); 16 | Application.ThreadException += (sender, args) => HandleException(args.Exception); 17 | 18 | try 19 | { 20 | Config config = new Config(); 21 | config.LoadSettings(); 22 | if (ActivateRunningInstance()) 23 | return; 24 | 25 | Application.EnableVisualStyles(); 26 | 27 | var locale = config.GetString("locale"); 28 | if (!string.IsNullOrEmpty(locale)) 29 | System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo(locale); 30 | 31 | var mainForm = new MainForm(config); 32 | if (Environment.CommandLine.Contains(WinServiceSwitch)) 33 | WinService.Start(mainForm); 34 | else 35 | Application.Run(mainForm); 36 | } 37 | catch (Exception ex) 38 | { 39 | HandleException(ex); 40 | } 41 | } 42 | #endregion 43 | 44 | #region ActivateRunningInstance() 45 | private static bool ActivateRunningInstance() 46 | { 47 | // check for processes to avoid unnecessary waiting for a TCP timeout 48 | var procList1 = System.Diagnostics.Process.GetProcessesByName("extraQL"); 49 | var procList2 = System.Diagnostics.Process.GetProcessesByName("extraQL.vshost"); // when started through VisualStudio debugger 50 | if (procList1.Length + procList2.Length <= 1) 51 | return false; 52 | 53 | // try to connect to a running instance 54 | using (var client = new XWebClient(5000)) 55 | { 56 | try 57 | { 58 | var servlet = Environment.CommandLine.Contains(BackgroundSwitch) ? "version" : "bringToFront"; 59 | var result = client.DownloadString("http://127.0.0.1:27963/" + servlet); 60 | if (result != null) 61 | return true; 62 | } 63 | catch 64 | { 65 | } 66 | } 67 | return false; 68 | } 69 | #endregion 70 | 71 | #region HandleException() 72 | private static void HandleException(Exception ex) 73 | { 74 | MessageBox.Show(ex.ToString(), "Program failure", MessageBoxButtons.OK, MessageBoxIcon.Error); 75 | } 76 | #endregion 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /source/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("extraQL")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("extraQL")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("aa997525-8541-4c2d-98b7-c8f2fa848866")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /source/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ExtraQL.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ExtraQL.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | internal static System.Drawing.Bitmap bg { 67 | get { 68 | object obj = ResourceManager.GetObject("bg", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized string similar to Your user iterface files are older than the web.pak file. 75 | ///Do you want to user the newer default UI (English)? 76 | /// 77 | ///(Use Shift+No to not stop asking). 78 | /// 79 | internal static string MainForm_StaleWebPak { 80 | get { 81 | return ResourceManager.GetString("MainForm_StaleWebPak", resourceCulture); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /source/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 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 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\resources\background.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | 125 | 126 | Your user iterface files are older than the web.pak file. 127 | Do you want to user the newer default UI (English)? 128 | 129 | (Use Shift+No to not stop asking) 130 | 131 | -------------------------------------------------------------------------------- /source/ScriptRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace ExtraQL 8 | { 9 | public class ScriptRepository 10 | { 11 | private readonly Dictionary scriptById = new Dictionary(); 12 | 13 | #region ctor() 14 | 15 | public ScriptRepository(string baseDir) 16 | { 17 | Log = txt => { }; 18 | ScriptDir = baseDir + "/scripts"; 19 | } 20 | 21 | #endregion 22 | 23 | public Action Log { get; set; } 24 | 25 | public string ScriptDir { get; } 26 | 27 | #region RegisterScripts() 28 | 29 | public void RegisterScripts() 30 | { 31 | foreach (string scriptPath in Directory.GetFiles(ScriptDir, "*.js")) 32 | { 33 | string localCode = File.ReadAllText(scriptPath); 34 | ScriptHeaderFields localMeta = ParseHeaderFields(localCode); 35 | var scriptInfo = new ScriptInfo(scriptPath, localCode, localMeta); 36 | scriptById[scriptInfo.Id] = scriptInfo; 37 | } 38 | } 39 | 40 | #endregion 41 | 42 | #region ParseHeaderFields() 43 | 44 | private ScriptHeaderFields ParseHeaderFields(string script) 45 | { 46 | var metadata = new ScriptHeaderFields(); 47 | int start = script.IndexOf("// ==UserScript=="); 48 | int end = script.IndexOf("// ==/UserScript=="); 49 | if (start < 0 || end < 0) 50 | { 51 | start = script.IndexOf("/*"); 52 | end = script.IndexOf("*/", start + 1); 53 | } 54 | if (start >= 0 && end >= 0) 55 | { 56 | var regex = new Regex("^\\s*//\\s*@(\\w+)\\s+(.*?)\\s*$"); 57 | string[] lines = script.Substring(start, end - start + 1).Split('\n'); 58 | foreach (string line in lines) 59 | { 60 | Match match = regex.Match(line); 61 | if (!match.Success) 62 | continue; 63 | string key = match.Groups[1].Value; 64 | string value = match.Groups[2].Value; 65 | 66 | metadata.Add(key, value); 67 | } 68 | } 69 | return metadata; 70 | } 71 | 72 | #endregion 73 | 74 | #region GetScripts() 75 | 76 | public List GetScripts() 77 | { 78 | return new List(scriptById.Values); 79 | } 80 | 81 | #endregion 82 | 83 | #region GetScriptByIdOrUrl() 84 | 85 | public ScriptInfo GetScriptByIdOrUrl(string scriptIdOrUrl) 86 | { 87 | ScriptInfo script; 88 | if (scriptById.TryGetValue(scriptIdOrUrl, out script)) 89 | { 90 | // refresh script info it file was modified locally (e.g. while developing) 91 | if (script.Timestamp < new FileInfo(script.Filepath).LastWriteTimeUtc.Ticks) 92 | { 93 | string content = File.ReadAllText(script.Filepath); 94 | ScriptHeaderFields meta = ParseHeaderFields(content); 95 | script = new ScriptInfo(script.Filepath, content, meta); 96 | scriptById[scriptIdOrUrl] = script; 97 | } 98 | return script; 99 | } 100 | 101 | return null; 102 | } 103 | 104 | #endregion 105 | } 106 | 107 | #region class ScriptInfo 108 | 109 | public class ScriptInfo 110 | { 111 | public readonly string Code; 112 | public readonly string Filepath; 113 | public readonly string Id; 114 | public readonly bool IsUserscript; 115 | public readonly ScriptHeaderFields Metadata; 116 | public readonly string Name; 117 | public readonly long Timestamp; 118 | 119 | public ScriptInfo(string filepath, string code, ScriptHeaderFields metadata) 120 | { 121 | string fileName = Path.GetFileName(filepath) ?? ""; 122 | string baseName = StripAllExtenstions(fileName); 123 | Id = metadata.Get("id") ?? baseName; 124 | Name = CleanupName(metadata.Get("name") ?? baseName); 125 | Filepath = filepath; 126 | Timestamp = new FileInfo(filepath).LastWriteTimeUtc.Ticks; 127 | Metadata = metadata; 128 | Code = code; 129 | IsUserscript = filepath.EndsWith(".usr.js"); 130 | } 131 | 132 | private string StripAllExtenstions(string scriptfile) 133 | { 134 | string filename = Path.GetFileName(scriptfile) ?? ""; 135 | int idx = filename.IndexOf('.'); 136 | return idx < 0 ? filename : filename.Substring(0, idx); 137 | } 138 | 139 | private string CleanupName(string value) 140 | { 141 | var prefixes = new[] {"ql ", "quakelive ", "quake live "}; 142 | string lower = value.ToLower(); 143 | foreach (string prefix in prefixes) 144 | { 145 | if (lower.StartsWith(prefix)) 146 | return value.Substring(prefix.Length); 147 | } 148 | return value; 149 | } 150 | 151 | public override string ToString() 152 | { 153 | string text = this.Name; 154 | if (string.IsNullOrEmpty(text)) 155 | text = Path.GetFileName(this.Filepath) ?? ""; 156 | return text; 157 | } 158 | } 159 | 160 | #endregion 161 | 162 | #region class ScriptHeaderFields 163 | 164 | public class ScriptHeaderFields 165 | { 166 | private readonly Dictionary> data = new Dictionary>(); 167 | 168 | public IEnumerable Keys 169 | { 170 | get { return data.Keys; } 171 | } 172 | 173 | public void Add(string key, string value) 174 | { 175 | if (!data.ContainsKey(key)) 176 | data.Add(key, new List()); 177 | List values = data[key]; 178 | values.Add(value); 179 | } 180 | 181 | public string Get(string key, int index = 0) 182 | { 183 | if (!data.ContainsKey(key)) 184 | return null; 185 | List values = data[key]; 186 | return index < values.Count ? values[index] : null; 187 | } 188 | 189 | public string Get(string key, string separator) 190 | { 191 | if (!data.ContainsKey(key)) 192 | return ""; 193 | List values = data[key]; 194 | var sb = new StringBuilder(); 195 | foreach (var value in values) 196 | { 197 | if (sb.Length > 0) sb.Append(separator); 198 | sb.Append(value); 199 | } 200 | return sb.ToString(); 201 | } 202 | } 203 | 204 | #endregion 205 | } -------------------------------------------------------------------------------- /source/Steamworks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using System.Windows.Forms; 6 | 7 | namespace ExtraQL 8 | { 9 | class Steamworks : IDisposable 10 | { 11 | #region Interop 12 | 13 | [DllImport("steam_api.dll", CallingConvention = CallingConvention.Cdecl, CharSet=CharSet.Ansi)] 14 | private extern static bool SteamAPI_Init(); 15 | 16 | [DllImport("steam_api.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 17 | private extern static bool SteamAPI_IsSteamRunning(); 18 | 19 | [DllImport("steam_api.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 20 | private extern static bool SteamAPI_Shutdown(); 21 | 22 | 23 | 24 | [DllImport("steam_api.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 25 | private extern static IntPtr SteamUser(); 26 | 27 | [DllImport("steam_api.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 28 | private static extern ulong SteamAPI_ISteamUser_GetSteamID(IntPtr instancePtr); 29 | 30 | 31 | 32 | [DllImport("steam_api.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 33 | private extern static IntPtr SteamFriends(); 34 | 35 | [DllImport("steam_api.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 36 | private extern static void SteamAPI_ISteamFriends_SetPersonaName(IntPtr handle, byte[] utf8name); 37 | 38 | #endregion 39 | 40 | // using the NVIDIA.SteamLauncher app-id (Config category) to prevent anything from showing up in the friend list nor block starting games 41 | public ulong AppID { get; set; } = 236600; 42 | 43 | private bool initialized; 44 | 45 | #region Dispose() 46 | 47 | ~Steamworks() 48 | { 49 | this.Dispose(false); 50 | } 51 | 52 | public void Dispose() 53 | { 54 | GC.SuppressFinalize(this); 55 | this.Dispose(true); 56 | } 57 | 58 | protected virtual void Dispose(bool disposing) 59 | { 60 | if (initialized) 61 | { 62 | SteamAPI_Shutdown(); 63 | initialized = false; 64 | } 65 | } 66 | #endregion 67 | 68 | public bool IsSteamRunning() 69 | { 70 | return SteamAPI_IsSteamRunning(); 71 | } 72 | 73 | private bool EnsureInit() 74 | { 75 | if (this.initialized) 76 | { 77 | if (SteamFriends() != IntPtr.Zero) 78 | return true; 79 | SteamAPI_Shutdown(); 80 | } 81 | 82 | string dllDir = Application.StartupPath + "\\"; 83 | 84 | 85 | // try NVIDIA.SteamLauncher AppID 86 | File.WriteAllText(dllDir + "steam_appid.txt", "236600"); 87 | this.initialized = SteamAPI_Init(); 88 | 89 | // try the configured AppID 90 | if (!this.initialized) 91 | { 92 | File.WriteAllText(dllDir + "steam_appid.txt", AppID.ToString()); 93 | this.initialized = SteamAPI_Init(); 94 | } 95 | 96 | // fallback to Steamworks SDK Redist AppID 97 | if (!this.initialized && AppID != 1007) 98 | { 99 | File.WriteAllText(dllDir + "steam_appid.txt", "1007"); 100 | this.initialized = SteamAPI_Init(); 101 | } 102 | 103 | return this.initialized; 104 | } 105 | 106 | public bool SetName(string name) 107 | { 108 | if (!EnsureInit()) 109 | return false; 110 | 111 | var handle = SteamFriends(); 112 | if (handle == IntPtr.Zero) 113 | return false; 114 | var cName = Encoding.UTF8.GetBytes(name + "\0"); 115 | SteamAPI_ISteamFriends_SetPersonaName(handle, cName); 116 | return true; 117 | } 118 | 119 | public ulong GetUserID() 120 | { 121 | if (!EnsureInit()) 122 | return 0; 123 | 124 | var handle = SteamUser(); 125 | return handle == IntPtr.Zero ? 0 : SteamAPI_ISteamUser_GetSteamID(handle); 126 | } 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /source/UpdateForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace ExtraQL 2 | { 3 | partial class UpdateForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.lblText = new System.Windows.Forms.Label(); 32 | this.SuspendLayout(); 33 | // 34 | // lblText 35 | // 36 | this.lblText.AutoSize = true; 37 | this.lblText.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 38 | this.lblText.Location = new System.Drawing.Point(13, 18); 39 | this.lblText.Name = "lblText"; 40 | this.lblText.Size = new System.Drawing.Size(17, 16); 41 | this.lblText.TabIndex = 0; 42 | this.lblText.Text = "..."; 43 | // 44 | // UpdateForm 45 | // 46 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 47 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 48 | this.ClientSize = new System.Drawing.Size(303, 52); 49 | this.ControlBox = false; 50 | this.Controls.Add(this.lblText); 51 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; 52 | this.Name = "UpdateForm"; 53 | this.ShowInTaskbar = false; 54 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 55 | this.Text = "extraQL"; 56 | this.ResumeLayout(false); 57 | this.PerformLayout(); 58 | 59 | } 60 | 61 | #endregion 62 | 63 | private System.Windows.Forms.Label lblText; 64 | 65 | } 66 | } -------------------------------------------------------------------------------- /source/UpdateForm.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace ExtraQL 4 | { 5 | public partial class UpdateForm : Form 6 | { 7 | public UpdateForm() 8 | { 9 | InitializeComponent(); 10 | } 11 | 12 | public string Message 13 | { 14 | get { return this.lblText.Text; } 15 | set 16 | { 17 | this.lblText.Text = value; 18 | Application.DoEvents(); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/UpdateForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 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 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /source/Win32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace ExtraQL 7 | { 8 | public static class Win32 9 | { 10 | [DllImport("user32.dll")] 11 | public static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam); 12 | 13 | [DllImport("user32.dll")] 14 | public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, string lParam); 15 | 16 | [DllImport("user32.dll")] 17 | public static extern int PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam); 18 | 19 | [DllImport("user32.dll")] 20 | public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags); 21 | 22 | [DllImport("user32.dll")] 23 | public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); 24 | 25 | [DllImport("user32")] 26 | public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i); 27 | 28 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 29 | public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); 30 | 31 | [DllImport("user32.dll")] 32 | public static extern bool AdjustWindowRect(ref RECT rect, uint dwStyle, bool bMenu); 33 | 34 | [DllImport("user32.dll")] 35 | public static extern uint GetWindowLong(IntPtr hWnd, int nIndex); 36 | 37 | [DllImport("user32.dll")] 38 | public static extern bool ReleaseCapture(); 39 | 40 | [DllImport("user32.dll")] 41 | public static extern bool IsWindowUnicode(IntPtr hWnd); 42 | 43 | [DllImport("user32.dll")] 44 | public static extern bool ShowWindow(IntPtr handle, int flags); 45 | 46 | [DllImport("user32.dll")] 47 | public static extern bool SetActiveWindow(IntPtr handle); 48 | 49 | [DllImport("user32.dll")] 50 | public static extern bool SetForegroundWindow(IntPtr handle); 51 | 52 | [DllImport("user32.dll")] 53 | public static extern bool SetFocus(IntPtr handle); 54 | 55 | [DllImport("user32.dll")] 56 | public static extern bool SetCapture(IntPtr handle); 57 | 58 | [DllImport("user32.dll")] 59 | public static extern bool EnableWindow(IntPtr handle, bool enable); 60 | 61 | public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr lParam); 62 | 63 | public static int HWND_TOPMOST = -1; 64 | public static int HWND_NOTOPMOST = -2; 65 | 66 | public const int WM_ACTIVATE = 0x0006; 67 | public const int WM_NCACTIVATE = 0x0086; 68 | public const int WM_SETREDRAW = 0x000B; 69 | public const int WM_CLOSE = 0x0010; 70 | public const int WM_SHOWWINDOW = 0x0018; 71 | public const int WM_KEYDOWN = 0x0100; 72 | public const int WM_KEYUP = 0x0101; 73 | public const int WM_CHAR = 0x0102; 74 | public const int WM_UNICHAR = 0x0109; 75 | public const int WM_IME_CHAR = 0x0286; 76 | public const int WM_MOUSEMOVE = 0x0200; 77 | public const int WM_LBUTTONDOWN = 0x0201; 78 | public const int WM_LBUTTONUP = 0x0202; 79 | public const int WM_SETTEXT = 0x000C; 80 | public const int WM_NCLBUTTONDOWN = 0x00A1; 81 | public const int WM_EXITSIZEMOVE = 0x0232; 82 | public const int WM_SYSKEYDOWN = 0x0104; 83 | public const int WM_SYSKEYUP = 0x0105; 84 | 85 | public const int SW_SHOWNORMAL = 1; 86 | 87 | public const int WA_ACTIVE = 1; 88 | public const int WA_CLICKACTIVE = 2; 89 | 90 | public const int SWP_NOSIZE = 0x0001; 91 | public const int SWP_NOMOVE = 0x0002; 92 | public const int SWP_NOACTIVATE = 0x0010; 93 | public const int SWP_SHOWWINDOW = 0x0040; 94 | public const int SWP_HIDEWINDOW = 0x0080; 95 | public const int SWP_NOOWNERZORDER = 0x0200; 96 | public const int SWP_NOSENDCHANGING = 0x0400; 97 | public const int SWP_NOZORDER = 0x0004; 98 | 99 | public const int GWL_STYLE = -16; 100 | 101 | public const int WS_DISABLED = 0x08000000; 102 | 103 | public const int HT_CAPTION = 2; 104 | 105 | #region struct RECT 106 | 107 | [StructLayout(LayoutKind.Sequential)] 108 | public struct RECT 109 | { 110 | public int Left, Top, Right, Bottom; 111 | 112 | public int Height 113 | { 114 | get { return Bottom - Top; } 115 | set { Bottom = value + Top; } 116 | } 117 | 118 | public int Width 119 | { 120 | get { return Right - Left; } 121 | set { Right = value + Left; } 122 | } 123 | } 124 | 125 | #endregion 126 | 127 | #region GetChildWindows() 128 | 129 | public static List GetChildWindows(IntPtr parent) 130 | { 131 | var result = new List(); 132 | GCHandle listHandle = GCHandle.Alloc(result); 133 | try 134 | { 135 | EnumWindowProc childProc = EnumWindow; 136 | EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle)); 137 | } 138 | finally 139 | { 140 | if (listHandle.IsAllocated) 141 | listHandle.Free(); 142 | } 143 | return result; 144 | } 145 | 146 | /// 147 | /// Callback method to be used when enumerating windows. 148 | /// 149 | /// Handle of the next window 150 | /// Pointer to a GCHandle that holds a reference to the list to fill 151 | /// True to continue the enumeration, false to bail 152 | private static bool EnumWindow(IntPtr handle, IntPtr pointer) 153 | { 154 | GCHandle gch = GCHandle.FromIntPtr(pointer); 155 | var list = gch.Target as List; 156 | if (list == null) 157 | { 158 | throw new InvalidCastException("GCHandle Target could not be cast as List"); 159 | } 160 | list.Add(handle); 161 | // You can modify this to check to see if you want to cancel the operation, then return a null here 162 | return true; 163 | } 164 | 165 | #endregion 166 | 167 | [DllImport("kernel32.dll")] 168 | public static extern IntPtr LoadLibrary(string steamApiDll); 169 | 170 | [DllImport("kernel32.dll")] 171 | public static extern IntPtr FreeLibrary(IntPtr hModule); 172 | 173 | 174 | [DllImport("kernel32.dll")] 175 | public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 176 | 177 | } 178 | } -------------------------------------------------------------------------------- /source/WinService.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%1" == "install" goto install 3 | if "%1" == "delete" goto uninstall 4 | if "%1" == "start" goto start 5 | if "%1" == "stop" goto stop 6 | if "%1" == "restart" goto restart 7 | 8 | echo Usage: %~nx0 { install ^| delete ^| start ^| stop ^| restart } 9 | goto eof 10 | 11 | :install 12 | sc create extraQL DisplayName= "extraQL" binPath= "d:\sources\quakelive\extraql\bin\debug\extraql.exe -service" start= auto 13 | goto eof 14 | 15 | :uninstall 16 | sc delete extraQL 17 | goto eof 18 | 19 | :start 20 | net start extraQL 21 | goto eof 22 | 23 | :stop 24 | net stop extraQL 25 | goto eof 26 | 27 | :restart 28 | net stop extraQL 29 | net start extraQL 30 | goto eof 31 | 32 | :eof -------------------------------------------------------------------------------- /source/WinService.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceProcess; 2 | using System.Threading; 3 | using System.Windows.Forms; 4 | 5 | namespace ExtraQL 6 | { 7 | class WinService : ServiceBase 8 | { 9 | private static Form mainForm; 10 | 11 | internal static void Start(Form form) 12 | { 13 | mainForm = form; 14 | var servicesToRun = new ServiceBase[] { new WinService() }; 15 | ServiceBase.Run(servicesToRun); 16 | } 17 | 18 | private WinService() 19 | { 20 | this.ServiceName = "extraQL"; 21 | } 22 | 23 | protected override void OnStart(string[] args) 24 | { 25 | var uiThread = new Thread(() => Application.Run(mainForm)); 26 | uiThread.Start(); 27 | } 28 | 29 | protected override void OnStop() 30 | { 31 | mainForm.BeginInvoke((ThreadStart)(() => mainForm.Close())); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/XWebClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Reflection; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace ExtraQL 8 | { 9 | /// 10 | /// The .NET built-in WebClient class has numerous bugs in its timeout handling. 11 | /// This helper class emulates the WebClient API and adds a working timeout handling to it. 12 | /// - setting WebClient.Proxy=null works in many cases, but not all 13 | /// - even when overriding WebClient.GetWebRequest() and setting a timeout, it is on some PCs ignored in synchronous requests 14 | /// - even when overriding WebClient.GetWebRequest() and setting a timeout, it is always ignored in asynchronous requests 15 | /// In the above cases where the set timeout is ignored (e.g. when connecting to a non-opened port), the timeout happens after 20sec 16 | /// 17 | class XWebClient : IDisposable 18 | { 19 | public int Timeout { get; set; } 20 | public Encoding Encoding { get; set; } 21 | public event DownloadStringCompletedEventHandler DownloadStringCompleted; 22 | public event DownloadDataCompletedEventHandler DownloadDataCompleted; 23 | 24 | #region ctor, Dispose 25 | 26 | public XWebClient() : this(1000) 27 | { 28 | } 29 | 30 | public XWebClient(int timeout) 31 | { 32 | this.Timeout = timeout; 33 | } 34 | 35 | public void Dispose() 36 | { 37 | // just for syntax compatibility with WebClient class 38 | } 39 | #endregion 40 | 41 | #region DownloadString, DownloadStringAsync 42 | 43 | public string DownloadString(string url) 44 | { 45 | return this.DownloadString(new Uri(url)); 46 | } 47 | 48 | public string DownloadString(Uri url) 49 | { 50 | var request = new AsyncRequest(url, null); 51 | this.DownloadStringWithTimeout(request); 52 | if (request.StringResult.Error != null) 53 | throw request.StringResult.Error; 54 | return request.StringResult.Result; 55 | } 56 | 57 | public void DownloadStringAsync(Uri url, object state = null) 58 | { 59 | if (this.DownloadStringCompleted == null) 60 | return; 61 | var request = new AsyncRequest(url, state); 62 | ThreadPool.QueueUserWorkItem(this.DownloadStringWithTimeout, request); 63 | } 64 | 65 | private void DownloadStringWithTimeout(object asyncRequest) 66 | { 67 | var request = (AsyncRequest)asyncRequest; 68 | using (request) 69 | { 70 | request.DownloadStringAsync(this.Encoding); 71 | if (!request.WaitHandle.WaitOne(this.Timeout)) 72 | { 73 | var ex = new TimeoutException("Request timed out"); 74 | var internalCtor = typeof (DownloadStringCompletedEventArgs).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, 75 | new[] {typeof (string), typeof (Exception), typeof (bool), typeof (object)}, null); 76 | request.StringResult = (DownloadStringCompletedEventArgs) internalCtor.Invoke(new[] {null, ex, false, request.State}); 77 | } 78 | } 79 | 80 | var handler = this.DownloadStringCompleted; 81 | if (handler != null) 82 | handler(this, request.StringResult); 83 | } 84 | #endregion 85 | 86 | #region DownloadData, DownloadDataAsync 87 | 88 | public byte[] DownloadData(Uri url) 89 | { 90 | var request = new AsyncRequest(url, null); 91 | this.DownloadDataWithTimeout(request); 92 | if (request.DataResult.Error != null) 93 | throw request.DataResult.Error; 94 | return request.DataResult.Result; 95 | } 96 | 97 | 98 | public void DownloadDataAsync(Uri url, object state) 99 | { 100 | if (this.DownloadDataCompleted == null) 101 | return; 102 | var request = new AsyncRequest(url, state); 103 | ThreadPool.QueueUserWorkItem(this.DownloadDataWithTimeout, request); 104 | } 105 | 106 | private void DownloadDataWithTimeout(object asyncRequest) 107 | { 108 | var request = (AsyncRequest)asyncRequest; 109 | using (request) 110 | { 111 | request.DownloadDataAsync(); 112 | if (!request.WaitHandle.WaitOne(this.Timeout)) 113 | { 114 | var ex = new TimeoutException("Request timed out"); 115 | var internalCtor = typeof(DownloadDataCompletedEventArgs).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, 116 | new[] { typeof(byte[]), typeof(Exception), typeof(bool), typeof(object) }, null); 117 | request.DataResult = (DownloadDataCompletedEventArgs)internalCtor.Invoke(new [] { null, ex, false, request.State }); 118 | } 119 | } 120 | 121 | var handler = this.DownloadDataCompleted; 122 | if (handler != null) 123 | handler(this, request.DataResult); 124 | } 125 | #endregion 126 | 127 | #region class AsyncRequest 128 | class AsyncRequest : IDisposable 129 | { 130 | public readonly Uri Url; 131 | public readonly object State; 132 | private readonly WebClient webClient; 133 | 134 | public readonly ManualResetEvent WaitHandle = new ManualResetEvent(false); 135 | public DownloadDataCompletedEventArgs DataResult; 136 | public DownloadStringCompletedEventArgs StringResult; 137 | 138 | public AsyncRequest(Uri url, object state) 139 | { 140 | this.Url = url; 141 | this.State = state; 142 | this.webClient = new WebClient(); 143 | this.webClient.Proxy = null; 144 | this.webClient.DownloadStringCompleted += DownloadStringCompleted; 145 | this.webClient.DownloadDataCompleted += DownloadDataCompleted; 146 | } 147 | 148 | public void DownloadStringAsync(Encoding encoding) 149 | { 150 | if (encoding != null) 151 | this.webClient.Encoding = encoding; 152 | this.webClient.DownloadStringAsync(this.Url, this.State); 153 | } 154 | 155 | public void DownloadDataAsync() 156 | { 157 | this.webClient.DownloadDataAsync(this.Url, this.State); 158 | } 159 | 160 | private void DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) 161 | { 162 | this.StringResult = e; 163 | this.WaitHandle.Set(); 164 | } 165 | 166 | private void DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e) 167 | { 168 | this.DataResult = e; 169 | this.WaitHandle.Set(); 170 | } 171 | 172 | public void Dispose() 173 | { 174 | this.webClient.Dispose(); 175 | } 176 | } 177 | #endregion 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /source/create-extraQL-zip.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal EnableExtensions 3 | 4 | cd /d %~dp0 5 | set zipper="c:\program files\7-Zip\7z.exe" 6 | set abs=%cd% 7 | 8 | rem package binaries 9 | call :CodeSigning 10 | cd %abs% 11 | if errorlevel 1 goto error 12 | if exist extraQL.zip del extraQL.zip 13 | if errorlevel 1 goto error 14 | if exist extraQL\ rmdir /s /q extraQL 15 | mkdir extraQL 16 | mkdir extraQL\scripts 17 | mkdir extraQL\images 18 | mkdir extraQL\de 19 | mkdir extraQL\ru 20 | echo copying... 21 | xcopy "%abs%\bin\Debug\extraQL.exe" extraQL\ 22 | xcopy "%abs%\bin\Debug\de\extraQL.resources.dll" extraQL\de\ 23 | xcopy "%abs%\bin\Debug\ru\extraQL.resources.dll" extraQL\ru\ 24 | xcopy "%abs%\bin\Debug\steam_api.dll" extraQL\ 25 | xcopy "%abs%\bin\Debug\steam_appid.txt" extraQL\ 26 | xcopy "%abs%\scripts" extraQL\scripts 27 | xcopy /s "%abs%\images" extraQL\images 28 | 29 | %zipper% a -tzip extraQL.zip "extraQL" -x!extraQL\scripts\rosterGroup.usr.js 30 | if errorlevel 1 goto error 31 | rmdir /s /q extraQL 32 | goto :success 33 | 34 | :CodeSigning 35 | rem ----------------------------- 36 | rem If you want to digitally sign the generated .exe and .dll files, 37 | rem you need to have your code signing certificate installed in the Windows certificate storage 38 | rem ----------------------------- 39 | set signtool="C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\signtool.exe" 40 | set oldcd=%cd% 41 | cd %abs%\bin\Debug 42 | set files=extraQL.exe de\extraQL.resources.dll ru\extraQL.resources.dll 43 | %signtool% sign /a /t "http://timestamp.comodoca.com/authenticode" %files% 44 | goto :eof 45 | 46 | :success 47 | echo. 48 | echo SUCCESS !!! 49 | echo. 50 | pause 51 | goto :eof 52 | 53 | :error 54 | echo. 55 | echo FAILED !!! 56 | echo. 57 | pause 58 | goto :eof 59 | 60 | -------------------------------------------------------------------------------- /source/extraQL.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /source/extraQL.csproj.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | 5F3CD06B-09F5-4827-85B2-5C5F43445F32/d:Properties/f:Resources.resx -------------------------------------------------------------------------------- /source/extraQL.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/extraQL.ico -------------------------------------------------------------------------------- /source/extraQL.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32505.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "extraQL", "extraQL.csproj", "{5F3CD06B-09F5-4827-85B2-5C5F43445F32}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CF6DE332-2D7F-4144-87CB-C1DD8987DAB7}" 9 | ProjectSection(SolutionItems) = preProject 10 | ..\readme.md = ..\readme.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Debug|Mixed Platforms = Debug|Mixed Platforms 17 | Debug|x86 = Debug|x86 18 | Release|Any CPU = Release|Any CPU 19 | Release|Mixed Platforms = Release|Mixed Platforms 20 | Release|x86 = Release|x86 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 26 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Debug|Mixed Platforms.Build.0 = Debug|x86 27 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Debug|x86.ActiveCfg = Debug|x86 28 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Debug|x86.Build.0 = Debug|x86 29 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 32 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Release|Mixed Platforms.Build.0 = Release|Any CPU 33 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Release|x86.ActiveCfg = Release|x86 34 | {5F3CD06B-09F5-4827-85B2-5C5F43445F32}.Release|x86.Build.0 = Release|x86 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {AE6DFE68-32B1-4576-83EB-AC5AEE6525E8} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /source/images/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/bottom.png -------------------------------------------------------------------------------- /source/images/bottomleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/bottomleft.png -------------------------------------------------------------------------------- /source/images/bottomright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/bottomright.png -------------------------------------------------------------------------------- /source/images/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/left.png -------------------------------------------------------------------------------- /source/images/maxsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/maxsize.png -------------------------------------------------------------------------------- /source/images/minsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/minsize.png -------------------------------------------------------------------------------- /source/images/offline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/offline.jpg -------------------------------------------------------------------------------- /source/images/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/right.png -------------------------------------------------------------------------------- /source/images/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/top.png -------------------------------------------------------------------------------- /source/images/topleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/topleft.png -------------------------------------------------------------------------------- /source/images/topright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/images/topright.png -------------------------------------------------------------------------------- /source/resources/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/resources/background.png -------------------------------------------------------------------------------- /source/resources/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/resources/close.png -------------------------------------------------------------------------------- /source/resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/resources/logo.png -------------------------------------------------------------------------------- /source/resources/minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/resources/minimize.png -------------------------------------------------------------------------------- /source/resources/trayIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/resources/trayIcon.png -------------------------------------------------------------------------------- /source/scripts/attic/antiAjax.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 288341 3 | // @name Quake Live Anti In-Game AJAX (QLHM Edition) 4 | // @version 1.1 5 | // @author PredatH0r 6 | // @description Reduce in-game lags by disabling the browser's HTTP requests. 7 | // @include http://*.quakelive.com/* 8 | // @exclude http://*.quakelive.com/forum* 9 | // @unwrap 10 | // ==/UserScript== 11 | 12 | /* 13 | 14 | WARNING: This is not a general-purpose script! 15 | When enabled, it WILL screw up your user interface after entering a game. 16 | To restore your UI, execute "/web_reload" in your console. 17 | To enable the script, move it to the "script" folder 18 | 19 | Version 1.1 20 | - allowing comma separated list of white-listed URLs in sv_ajaxWhitelist 21 | - execute /web_reload when leaving a game 22 | 23 | Version 1.0 24 | - crude prototype 25 | 26 | */ 27 | 28 | (function (win) { 29 | var ENABLED = true; 30 | 31 | var oldHandlers = {}; 32 | var handlerNames = ["IM_OnMessage", "IM_OnPresence", "IM_OnItemAdded", "IM_OnItemRemoved"]; 33 | var allowedRequests = ["/request/invite", "/startamatch/invites"]; 34 | var $ = win.$; 35 | 36 | function init() { 37 | if (!ENABLED) 38 | return; 39 | 40 | // hook into XMPP events 41 | for (var i = 0; i < handlerNames.length; i++) { 42 | var eventName = handlerNames[i]; 43 | oldHandlers[eventName] = win[eventName]; 44 | var handler = new MyEventHandler(eventName); 45 | win[eventName] = MyEventHandler.prototype.Handle.bind(handler); 46 | } 47 | oldHandlers.jQuery_ajax = $.ajax; 48 | $.ajax = jQuery_ajax; 49 | 50 | // add whitelist URLs from CVAR 51 | var whitelist = quakelive.cvars.Get("cl_ajaxWhitelist").value; 52 | if (whitelist) { 53 | $.each(whitelist.split(","), function (index, item) { 54 | allowedRequests.push(item); 55 | }); 56 | } 57 | 58 | quakelive.AddHook('OnGameModeStarted', function () { 59 | quakelive.cfgUpdater.pauseCommit = true; 60 | }); 61 | quakelive.AddHook('OnGameModeEnded', function () { 62 | quakelive.cfgUpdater.pauseCommit = false; 63 | quakelive.cfgUpdater.Commit(); 64 | qz_instance.SendGameCommand("web_reload"); 65 | }); 66 | } 67 | 68 | function MyEventHandler(eventName) { 69 | this.eventName = eventName; 70 | } 71 | 72 | MyEventHandler.prototype.Handle = function () { 73 | try { 74 | if (quakelive.IsGameRunning()) 75 | return; 76 | var args = Array.prototype.slice.call(arguments); 77 | oldHandlers[this.eventName].apply(win, args); 78 | } catch (e) { 79 | win.console.log(e); 80 | } 81 | }; 82 | 83 | function jQuery_ajax(arg /* ... */) { 84 | try { 85 | var makeCall = true; 86 | if (quakelive.IsGameRunning()) { 87 | var url = typeof arg == "string" ? arg : arg.url; 88 | makeCall = false; 89 | for (var i = 0; i < allowedRequests.length; i++) { 90 | if (url.indexOf(allowedRequests[i]) === 0) { 91 | debug("sending ajax request: " + url); 92 | makeCall = true; 93 | break; 94 | } 95 | } 96 | } 97 | if (makeCall) 98 | return oldHandlers.jQuery_ajax.apply($, Array.prototype.slice.call(arguments)); 99 | } 100 | catch (e) { 101 | win.console.log(e); 102 | } 103 | return { 104 | done: function () { } 105 | , fail: function () { } 106 | }; 107 | } 108 | 109 | function debug(msg) { 110 | win.console.log(msg); 111 | } 112 | 113 | init(); 114 | })(window); -------------------------------------------------------------------------------- /source/scripts/attic/autoExec.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 188803 3 | // @name Game Start AutoExec/FullScreen 4 | // @version 2.1 5 | // @author PredatH0r 6 | // @description Automates execution of commands when starting/ending game-mode or switching fullscreen 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /* 11 | 12 | Version 2.0 13 | - contains the automation features of the previous Alt Menu Script, but without the UI modifications 14 | 15 | */ 16 | 17 | (function (win) { 18 | var VERSION = "2.0"; 19 | 20 | var window = win; 21 | var quakelive = win.quakelive; 22 | var $ = win.jQuery; 23 | var oldLaunchGame; 24 | 25 | function error(e) { 26 | window.console.log("ERROR - " + e); 27 | } 28 | 29 | function debug(msg) { 30 | //window.console.log("DEBUG - " + msg); 31 | } 32 | 33 | function init() { 34 | try { 35 | addStyle(".qkTextOption { position: absolute; left: 110px; width: 410px }"); 36 | 37 | HOOK_MANAGER.addMenuItem("Auto Exec...", showConsole); 38 | 39 | // install hooks 40 | quakelive.AddHook("OnGameModeStarted", OnGameModeStarted); 41 | quakelive.AddHook("OnGameStarted", OnGameStarted); 42 | quakelive.AddHook("OnGameModeEnded", OnGameModeEnded); 43 | oldLaunchGame = window.LaunchGame; 44 | window.LaunchGame = LaunchGame; 45 | } 46 | catch (e) { error(e); } 47 | } 48 | 49 | function addStyle() { 50 | var css = ""; 51 | for (var i = 0; i < arguments.length; i++) 52 | css += "\n" + arguments[i]; 53 | $("head").append(""); 54 | } 55 | 56 | function showConsole() { 57 | try { 58 | var out = []; 59 | out.push("
"); 60 | out.push("
"); 61 | 62 | var mode = parseInt(quakelive.cvars.Get("r_autoFullscreen", "0").value); 63 | out.push("Fullscreen / Windowed Mode"); 64 | out.push("
    "); 65 | out.push("
  • "); 66 | out.push(" Go fullscreen when joining a game (r_autoFullscreen=+1)
  • "); 67 | out.push("
  • "); 68 | out.push(" Go window mode when leaving a game (r_autoFullscreen=+2)
  • "); 69 | out.push("
  • "); 70 | out.push(" Use fast mode switching (NOT working with some GPUs) (r_autoFullscreen=+4)
  • "); 71 | out.push("
"); 72 | 73 | var gameStart = quakelive.cvars.Get("onGameStart", "").value; 74 | var gameEnd = quakelive.cvars.Get("onGameEnd", "").value; 75 | out.push("

Commands executed when entering / leaving a game"); 76 | out.push("
(e.g. com_maxfps 125;r_gamma 1)"); 77 | out.push("
    "); 78 | out.push("
  • onGameStart: "); 79 | out.push("
  • onGameEnd: "); 80 | out.push("
"); 81 | out.push("
"); 82 | out.push("
"); 83 | 84 | // Inject the console 85 | qlPrompt({ 86 | id: "qkPrompt", 87 | title: "Quake Live AutoExec" + " (v" + VERSION + ")", 88 | customWidth: 550, 89 | ok: handleConsoleOk, 90 | okLabel: "Ok", 91 | cancel: handleConsoleClose, 92 | cancelLabel: "Cancel", 93 | body: out.join("") 94 | }); 95 | 96 | // Wait for the prompt to get inserted then do stuff... 97 | window.setTimeout(function () { 98 | $("#modal-cancel").focus(); 99 | }); 100 | } 101 | catch (e) { 102 | error(e); 103 | handleConsoleClose(); 104 | } 105 | } 106 | 107 | function handleConsoleOk() { 108 | var val = 0; 109 | $("#qkConsole").find(".qkAutoFullscreen:checked").each(function (idx, item) { 110 | val += parseInt(item.value); 111 | }); 112 | quakelive.cvars.Set("r_autoFullscreen", "" + val, true, false); 113 | 114 | $("#qkConsole").find(".qkTextOption").each(function (idx, item) { 115 | quakelive.cvars.Set(item.name, '"' + item.value + '"', true, false); 116 | }); 117 | 118 | handleConsoleClose(); 119 | } 120 | 121 | function handleConsoleClose() { 122 | $("#qkPrompt").jqmHide(); 123 | } 124 | 125 | function LaunchGame(launchParams, serverInfo) { 126 | try { 127 | debug("LaunchGame() called"); 128 | autoSwitchToFullscreen(); 129 | oldLaunchGame.call(null, launchParams, serverInfo); 130 | } 131 | catch (e) { error(e); } 132 | } 133 | 134 | function OnGameModeStarted() { 135 | try { 136 | debug("OnGameModeStarted() called"); 137 | autoSwitchToFullscreen(); // fallback, if starting directly via /connect and bypassing UI 138 | } catch (e) { error(e); } 139 | } 140 | 141 | function OnGameStarted() { 142 | try { 143 | debug("OnGameStarted() called"); 144 | autoSwitchToFullscreen(); // fallback, if starting directly via /connect and bypassing UI 145 | qz_instance.SendGameCommand("vstr onGameStart"); 146 | } catch (e) { error(e); } 147 | } 148 | 149 | function autoSwitchToFullscreen() { 150 | var auto = quakelive.cvars.Get("r_autoFullscreen").value; 151 | if (auto != "" && (parseInt(auto) & 0x01) && quakelive.cvars.Get("r_fullscreen").value != "1") { 152 | debug("auto switching to fullscreen"); 153 | // use both JS and QZ to avoid timing issues and make sure the value sticks 154 | quakelive.cvars.Set("r_fullscreen", "1", false, false); 155 | var fast = (parseInt(auto) & 0x04) ? " fast" : ""; 156 | qz_instance.SendGameCommand("seta r_fullscreen 1;vid_restart" + fast); 157 | } 158 | } 159 | 160 | function OnGameModeEnded() { 161 | try { 162 | debug("OnGameModeEnded() called"); 163 | var auto = quakelive.cvars.Get("r_autoFullscreen").value; 164 | if (auto != "" && (parseInt(auto) & 0x02) && quakelive.cvars.Get("r_fullscreen").value != "0") { 165 | debug("auto switching to windowed mode"); 166 | // use both JS and QZ to avoid timing issues and make sure the value sticks 167 | quakelive.cvars.Set("r_fullscreen", "0", false, false); 168 | var fast = (parseInt(auto) & 0x04) ? " fast" : ""; 169 | qz_instance.SendGameCommand("seta r_fullscreen 0;vid_restart" + fast); 170 | } 171 | qz_instance.SendGameCommand("vstr onGameEnd"); 172 | } catch (e) { error(e); } 173 | } 174 | 175 | 176 | init(); 177 | 178 | })(window); -------------------------------------------------------------------------------- /source/scripts/attic/autoInvite.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 186664 3 | // @name Auto Invite 4 | // @version 2.0 5 | // @author PredatH0r 6 | // @description Your friends will be able to write the word "invite" to you, and automatically get a real invite to the server you're on. 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /* 11 | This script is a modified version of flugsio's "Quake Live Pro Auto Invite" 12 | (http://userscripts.org/scripts/show/107333). 13 | It is now compatible with QLHM and the Quake Live standalone client. 14 | */ 15 | 16 | (function (win) { // scope 17 | var quakelive = win.quakelive; 18 | var inHook = false; 19 | function installHook() { 20 | try { 21 | quakelive.AddHook('IM_OnMessage', function(json) { 22 | try { 23 | if (inHook || !quakelive.IsIngameClient()) { 24 | return; 25 | } 26 | inHook = true; 27 | var msg = quakelive.Eval(json); 28 | var friend = quakelive.modules.friends.roster.GetIndexByName(msg.who); 29 | var roster = quakelive.modules.friends.roster.fullRoster[friend]; 30 | 31 | if (msg.what == 'invite') { 32 | roster.FriendsContextMenuHandler('invite', roster.node); 33 | qz_instance.SendGameCommand("echo for " + msg.who); 34 | } 35 | } 36 | catch(ex) { 37 | } 38 | finally { 39 | inHook = false; 40 | } 41 | }); 42 | } 43 | catch(e) {} 44 | }; 45 | 46 | installHook(); 47 | })(window); -------------------------------------------------------------------------------- /source/scripts/attic/demoBrowser.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id demoBrowser 3 | // @name Demo Browser 4 | // @version 1.0 5 | // @author PredatH0r 6 | // @description Browse your local demos and play them with a click. Adds an item to the "Play" menu. 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /* 11 | 12 | Version 1.0 13 | - first attempt 14 | 15 | */ 16 | 17 | (function (win) { 18 | var VERSION = "1.0"; 19 | 20 | // external globals 21 | var window = win; 22 | var $ = win.jQuery; 23 | var extraQL = win.extraQL; 24 | var qz_instance = win.qz_instance; 25 | 26 | // private variables 27 | var demos = []; 28 | 29 | function init() { 30 | try { 31 | extraQL.addStyle( 32 | "#demoBrowserBody { text-align: left; }", 33 | "#demoBrowserBody tbody tr:hover { background-color: #CCC; }", 34 | "#demoBrowserList { max-height: 600px; overflow: auto; }" 35 | ); 36 | window.demoBrowser_showConsole = showConsole; 37 | window.nav.navbar["Play"].submenu["Browse Demos"] = { "class": "ingame_only", callback: "demoBrowser_showConsole()" }; 38 | } 39 | catch (e) { error(e); } 40 | } 41 | 42 | function showConsole() { 43 | try { 44 | var out = []; 45 | out.push("
"); 46 | out.push(""); 47 | out.push(""); 48 | out.push(" (press ENTER to search; use SPACE to separate multiple keywords)"); 49 | out.push("

"); 50 | out.push("
"); 51 | out.push(""); 52 | out.push(""); 53 | out.push(""); 54 | out.push(""); 55 | out.push("
FileDate
"); 56 | out.push("
"); 57 | out.push("
"); 58 | $.getJSON(extraQL.BASE_URL + "demos", fillDemoList); 59 | 60 | // Inject the console 61 | qlPrompt({ 62 | id: "demoBrowser", 63 | title: "Demo Browser" + " (v" + VERSION + ")", 64 | customWidth: 800, 65 | body: out.join(""), 66 | hideButtons: false, 67 | cancelLabel: "Close" 68 | }); 69 | 70 | // Wait for the prompt to get inserted then do stuff... 71 | window.setTimeout(function () { 72 | var $filter = $("#demoBrowser_filter"); 73 | $filter.focus(); 74 | $filter.keydown(function(e) { 75 | if (e.keyCode == 13) { 76 | fillDemoList(demos); 77 | return false; 78 | } 79 | return true; 80 | }); 81 | $("#demoBrowser #modal-ok").css("display", "none"); 82 | $("#demoBrowser").css("top", "80px"); 83 | }); 84 | } 85 | catch (e) { 86 | extraQL.error(e); 87 | $("#demoBrowser").jqmHide(); 88 | } 89 | } 90 | 91 | function fillDemoList(data) { 92 | demos = data; 93 | 94 | var cond = $("#demoBrowser_filter").val(); 95 | var ands = cond.split(" "); 96 | var keywords = []; 97 | $.each(ands, function(i, word) { 98 | word = word.trim(); 99 | if (word) 100 | keywords.push(word); 101 | }); 102 | 103 | var $tbody = $("#demoBrowserList tbody"); 104 | $tbody.empty(); 105 | $.each(demos, function (idx, demo) { 106 | for (var i = 0; i < keywords.length; i++) { 107 | if (demo.file.indexOf(keywords[i]) < 0) 108 | return; 109 | } 110 | $tbody.append("" + demo.file + "" + demo.date.replace("T", "   ") + ""); 111 | }); 112 | $tbody.find("a").click(function () { 113 | playDemo($(this).text()); 114 | }); 115 | } 116 | 117 | function playDemo(file) { 118 | qz_instance.SendGameCommand("demo \"" + file + "\""); 119 | } 120 | 121 | 122 | init(); 123 | 124 | })(window); -------------------------------------------------------------------------------- /source/scripts/attic/docker.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id docker 3 | // @name Window Docker 4 | // @version 1.0 5 | // @author PredatH0r 6 | // @description Adds a menu that allows you to dock the QL window to a side of your screen using your full screen height or width 7 | // @note This script requires extraQL.exe to be running! 8 | // @unwrap 9 | // ==/UserScript== 10 | 11 | /* 12 | Adds a menu that allows you to 13 | - toggle full screen 14 | - dock the QL window to a side of your screen (with full desktop width or height) 15 | - move the QL window into a screen corner 16 | - set the window to the minimal size (1024x768) 17 | 18 | This script only works when extraQL.exe is running on your PC. 19 | 20 | CVARS: 21 | - web_dockWidth: width of QL window when docked to left/right screen border 22 | - web_dockHeight: height of QL window when docked to top/bottom screen border 23 | */ 24 | 25 | (function () { 26 | // external symbols 27 | var quakelive = window.quakelive; 28 | var extraQL = window.extraQL; 29 | 30 | var SET_FULLSCREEN = -1; 31 | var SET_MINSIZE = 0; 32 | var SET_LEFT = 0x01; 33 | var SET_RIGHT = 0x02; 34 | var SET_WIDTH = SET_LEFT | SET_RIGHT; 35 | var SET_TOP = 0x04; 36 | var SET_BOTTOM = 0x08; 37 | var SET_HEIGHT = SET_TOP | SET_BOTTOM; 38 | 39 | var URL_BASE = extraQL.BASE_URL; 40 | 41 | var CVAR_DOCKWIDTH = "web_dockWidth"; 42 | var CVAR_DOCKHEIGHT = "web_dockHeight"; 43 | 44 | function init() { 45 | if (!extraQL.isLocalServerRunning()) { 46 | extraQL.echo("^1docker^7 disabled: No local extraQL.exe running."); 47 | return; 48 | } 49 | 50 | extraQL.addStyle( 51 | "#winposControl { float: right; padding: 0px 30px; }", 52 | "#winposControl:hover { background-color: #323232; }", 53 | "#winposControl img { width: 18px; height: 18px; margin-top: 5px; margin-bottom: 5px; cursor: pointer; background-color: white; }", 54 | "#winposControl img:hover { background-color: #F6D000; }", 55 | "#winposPopup { position: absolute; top: 110px; left: 774px; display: none; background-color: #222; padding: 6px; z-index: 1000; }", 56 | "#winposPopup.hoverBar { display: block; }", 57 | "#winposPopup.hoverPopup { display: block; }", 58 | "#winposPopup img { width: 18px; height: 18px; margin: 3px; cursor: pointer; background-color: white; }", 59 | "#winposPopup img:hover { background-color: #F6D000; }" 60 | ); 61 | 62 | quakelive.AddHook("OnContentLoaded", onContentLoaded); 63 | } 64 | 65 | function onContentLoaded() { 66 | if ($("#winposControl").length) 67 | return; 68 | 69 | $("#tn_settings").after( 70 | "
  • " 71 | + " " 72 | + "
  • "); 73 | 74 | $("#qlv_content").append( 75 | "
    " 76 | + "
    " 77 | + " " 78 | + " " 79 | + " " 80 | + "
    " 81 | + "
    " 82 | + " " 83 | + " " 84 | + " " 85 | + "
    " 86 | + "
    " 87 | + " " 88 | + " " 89 | + " " 90 | + "
    " + 91 | "
    " 92 | ); 93 | var $popup = $("#winposPopup"); 94 | $("#winposControl").hover( 95 | function() { if (quakelive.cvars.Get("r_fullscreen").value == "0") $popup.addClass("hoverBar"); }, 96 | function () { window.setTimeout(function() { $popup.removeClass("hoverBar"); }, 100); }); 97 | $popup.hover( 98 | function () { $popup.addClass("hoverPopup"); }, 99 | function () { window.setTimeout(function () { $popup.removeClass("hoverPopup"); }, 500); }); 100 | $("#winposControl img").unbind("click").click(setMode); 101 | $("#winposPopup img").unbind("click").click(setMode); 102 | } 103 | 104 | function setMode() { 105 | move(parseInt($(this).data("mode"))); 106 | } 107 | 108 | function move(action) { 109 | if (action == SET_FULLSCREEN) { 110 | $.ajax({ url: URL_BASE + "toggleFullscreen" }) 111 | .fail(extraQLNotResponding); 112 | } else { 113 | if (quakelive.cvars.Get("r_fullscreen").value == "1") 114 | return; 115 | var width = quakelive.cvars.Get(CVAR_DOCKWIDTH).value; 116 | var height = quakelive.cvars.Get(CVAR_DOCKHEIGHT).value; 117 | $.ajax({ url: URL_BASE + "dockWindow?sides=" + action +"&w=" + width + "&h=" + height}) 118 | .fail(extraQLNotResponding); 119 | } 120 | } 121 | 122 | function extraQLNotResponding() { 123 | window.qlPrompt({ title: 'docker.js', body: "extraQL HTTP server is not responding", fatal: false, alert: true }); 124 | } 125 | 126 | init(); 127 | })(); 128 | -------------------------------------------------------------------------------- /source/scripts/attic/escaper.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 110327 3 | // @name Escaper 4 | // @version 1.6.1 5 | // @namespace phob.net 6 | // @author wn 7 | // @contributor PredatH0r 8 | // @description Press escape to close Quake Live's Game Summary, Live Game Info and notification popups 9 | // @unwrap 10 | // ==/UserScript== 11 | 12 | (function() { 13 | function QLE() { 14 | document.addEventListener("keyup", function(e) { 15 | if (e.keyCode != 27) return; 16 | window.quakelive.matchtip.HideMatchTooltip(-1); 17 | window.jQuery("#stats_details, #ql_notifier .ql_notice").remove(); 18 | }, false); 19 | } 20 | 21 | var scriptNode = document.createElement("script"); 22 | scriptNode.setAttribute("type", "text/javascript"); 23 | scriptNode.text = "(" + QLE.toString() + ")();"; 24 | document.body.appendChild(scriptNode); 25 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/esreality.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id esreality 3 | // @name ESReality.com Integration 4 | // @version 1.3 5 | // @author PredatH0r 6 | // @description Shows a list of esreality.com Quake Live forum posts 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /* 11 | 12 | Version 1.3 13 | - added workaround for external links in QL Steam build 14 | 15 | Version 1.2 16 | - fixed URL parameters to only load QL forum posts 17 | - fixed accumulating update timers 18 | 19 | Version 1.1 20 | - ensuring consistent order of tabs in the chat bar 21 | 22 | Version 1.0 23 | - first public release 24 | 25 | */ 26 | 27 | (function () { 28 | // external variables 29 | var quakelive = window.quakelive; 30 | var extraQL = window.extraQL; 31 | 32 | var URL_FORUM = "http://www.esreality.com/?a=post&forum=17"; 33 | var UPDATE_INTERVAL = 60000; 34 | var updateTimeoutHandle; 35 | 36 | function init() { 37 | if (!extraQL.BASE_URL) { 38 | extraQL.echo("^1esreality^7 disabled: Not connected to extraQL server"); 39 | return; 40 | } 41 | 42 | // delay init so that twitch, twitter, ESR and IRC scripts add items to chat menu bar in a defined order 43 | if (extraQL.hookVersion) // introduced at the same time as the addTabPage() "priority" param 44 | delayedInit(); 45 | else 46 | setTimeout(delayedInit, 1600); 47 | } 48 | 49 | function delayedInit() { 50 | onContentLoaded(); 51 | quakelive.AddHook("OnContentLoaded", onContentLoaded); 52 | quakelive.AddHook("OnGameModeEnded", updateForums); 53 | 54 | // the resizeLayout script's event handler will resize the
    for us 55 | if (typeof (window.onresize) == "function") 56 | window.onresize(); 57 | } 58 | 59 | function onContentLoaded() { 60 | if ($("#esreality").length) 61 | return; 62 | 63 | var fixedElementsHeight = 87; 64 | 65 | extraQL.addStyle( 66 | "#esreality { width: 300px; color: black; background-color: white; display: none; }", 67 | "#esrealityHeader { border-bottom: 1px solid #e8e8e8; padding: 9px 9px 8px 9px; }", 68 | "#esrealityHeader .headerText { font-size: 14px; font-weight: bold; line-height: 18px; }", 69 | "#esrealityHeader a { color: #A0220B; font-size: 14px; }", 70 | "#esrealityDetails { height: 32px; overflow: hidden; padding: 9px 6px; border-bottom: 1px solid #e8e8e8; }", 71 | "#esrealityContent { height: " + (550 - fixedElementsHeight) + "px; overflow: auto; }", 72 | "#esrealityContent div { padding: 3px 6px; max-height: 26px; overflow: hidden; }", 73 | "#esrealityContent .active { background-color: #ccc; }", 74 | "#esrealityContent a { color: black; text-decoration: none; }", 75 | "#esrealityContent a:hover { text-decoration: underline; }" 76 | ); 77 | 78 | var content = 79 | "
    " + 80 | "
    esreality.com Quake Live Forum
    " + 81 | "
    " + 82 | "
    " + 83 | "
    "; 84 | extraQL.addTabPage("esreality", "ESR", content, undefined, 300); 85 | 86 | $("#esrealityShowForums").click(function () { 87 | $("#esrealityHeader a").removeClass("active"); 88 | $(this).addClass("active"); 89 | updateForums(); 90 | }); 91 | 92 | updateForums(); 93 | } 94 | 95 | function updateForums() { 96 | if (quakelive.IsGameRunning()) 97 | return; 98 | 99 | if (updateTimeoutHandle) 100 | window.clearTimeout(updateTimeoutHandle); 101 | 102 | $.ajax({ 103 | url: extraQL.BASE_URL + "proxy", 104 | data: { url: URL_FORUM }, 105 | dataType: "text" 106 | }) 107 | .done(parseForum) 108 | .fail(function() { extraQL.log("Could not load esreality forum"); }); 109 | 110 | updateTimeoutHandle = window.setTimeout(updateForums, UPDATE_INTERVAL); 111 | } 112 | 113 | function parseForum(html) { 114 | try { 115 | var $forum = $("#esrealityContent"); 116 | $forum.empty(); 117 | 118 | // update post list 119 | $(html).find(".pl_row").each(function (i, item) { 120 | var $row = $(item); 121 | var $this = $row.children(".pl_main"); 122 | var $link = $this.children("span").children("a:first-child"); 123 | var lastCommentTime = $this.children("div").text(); 124 | var replies = $row.find(".pl_replies>div").text().trim(); 125 | var author = $row.find(".pl_author>div>a").text(); 126 | var descr = "Author: " + extraQL.escapeHtml(author) + ", Replies: " + replies + "
    " + lastCommentTime; 127 | $forum.append("" + 129 | "" + 130 | extraQL.escapeHtml($link.text()) +"
    "); 131 | }); 132 | $("#esrealityContent div").hover(showForumDetails); 133 | 134 | showDetailsForFirstEntry(); 135 | } catch (e) { 136 | extraQL.log(e); 137 | } 138 | } 139 | 140 | function showDetailsForFirstEntry() { 141 | var first = $("#esrealityContent>div")[0]; 142 | if (!first) { 143 | $("#esrealityStatus").text(""); 144 | } else { 145 | showForumDetails.apply(first); 146 | $(first).addClass("active"); 147 | } 148 | } 149 | 150 | function showForumDetails() { 151 | var $this = $(this); 152 | $("#esrealityDetails").html($this.data("status")); 153 | $("#esrealityContent div").removeClass("active"); 154 | $this.addClass("active"); 155 | } 156 | 157 | 158 | init(); 159 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/extGraphHist.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 134693 3 | // @name Quake Live Extended Graph History 4 | // @version 0.18 5 | // @namespace phob.net 6 | // @author wn 7 | // @description Keep more of your match history! 8 | // @include http://*.quakelive.com/* 9 | // @exclude http://*.quakelive.com/forum* 10 | // @run-at document-end 11 | // @updateURL https://userscripts.org/scripts/source/134693.meta.js 12 | // ==/UserScript== 13 | 14 | 15 | /** 16 | * Set up some stuff for user script updating 17 | */ 18 | var SCRIPT_NAME = "Quake Live Extended Graph History" 19 | , SCRIPT_VER = "0.18"; 20 | GM_updatingEnabled = "GM_updatingEnabled" in window ? GM_updatingEnabled : false; 21 | 22 | 23 | /** 24 | * Based on: 25 | * GM_ API emulation for Chrome; 2009, 2010 James Campos 26 | * cc-by-3.0; http://creativecommons.org/licenses/by/3.0/ 27 | */ 28 | if (window.chrome) { 29 | GM_getValue = function (aName, aDefaultValue) { 30 | var value = localStorage.getItem(aName); 31 | if (!value) return aDefaultValue; 32 | var type = value[0]; 33 | value = value.substring(1); 34 | switch (type) { 35 | case "b": 36 | return value == "true"; 37 | case "n": 38 | return Number(value); 39 | default: 40 | return value; 41 | } 42 | } 43 | GM_setValue = function (aName, aValue) { 44 | aValue = (typeof aValue)[0] + aValue; 45 | localStorage.setItem(aName, aValue); 46 | } 47 | GM_registerMenuCommand = function () { }; 48 | } 49 | 50 | 51 | /** 52 | * Use an auto-update script if integrated updating isn't enabled 53 | * http://userscripts.org/scripts/show/38017 54 | * NOTE: Modified to add the new version number to the upgrade prompt 55 | * and custom messages for Chrome users (requires a manual update). 56 | */ 57 | if (!GM_updatingEnabled) { 58 | // var AutoUpdater_134693 = { id: 134693, days: 1, name: SCRIPT_NAME, version: SCRIPT_VER, time: new Date().getTime(), call: function (response, secure) { GM_xmlhttpRequest({ method: "GET", url: "http" + (secure ? "s" : "") + "://userscripts.org/scripts/source/" + this.id + ".meta.js", onload: function (xpr) { AutoUpdater_134693.compare(xpr, response) }, onerror: function (xpr) { if (secure) { AutoUpdater_134693.call(response, false) } } }) }, enable: function () { GM_registerMenuCommand(this.name + ": Enable updates", function () { GM_setValue("updated_134693", new Date().getTime() + ""); AutoUpdater_134693.call(true, true) }) }, compareVersion: function (r_version, l_version) { var r_parts = r_version.split("."), l_parts = l_version.split("."), r_len = r_parts.length, l_len = l_parts.length, r = l = 0; for (var i = 0, len = (r_len > l_len ? r_len : l_len) ; i < len && r == l; ++i) { r = +(r_parts[i] || "0"); l = +(l_parts[i] || "0") } return (r !== l) ? r > l : false }, compare: function (xpr, response) { this.xversion = /\/\/\s*@version\s+(.+)\s*\n/i.exec(xpr.responseText); this.xname = /\/\/\s*@name\s+(.+)\s*\n/i.exec(xpr.responseText); if ((this.xversion) && (this.xname[1] == this.name)) { this.xversion = this.xversion[1]; this.xname = this.xname[1] } else { if ((xpr.responseText.match("the page you requested doesn't exist")) || (this.xname[1] != this.name)) { GM_setValue("updated_134693", "off") } return false } var updated = this.compareVersion(this.xversion, this.version); if (updated && confirm("A new version of " + this.xname + " is available.\nDo you wish to install the latest version (" + this.xversion + ")?")) { var path = "http://userscripts.org/scripts/source/" + this.id + ".user.js"; if (window.chrome) { prompt("This script can't be updated automatically in Chrome.\nPlease uninstall the old version, and navigate to the URL provided below.", path) } else { try { window.parent.location.href = path } catch (e) { } } } else { if (this.xversion && updated) { if (confirm("Do you want to turn off auto updating for this script?")) { GM_setValue("updated_134693", "off"); this.enable(); if (window.chrome) { alert("You will need to reinstall this script to enable auto-updating.") } else { alert("Automatic updates can be re-enabled for this script from the User Script Commands submenu.") } } } else { if (response) { alert("No updates available for " + this.name) } } } }, check: function () { if (GM_getValue("updated_134693", 0) == "off") { this.enable() } else { if (+this.time > (+GM_getValue("updated_134693", 0) + 1000 * 60 * 60 * 24 * this.days)) { GM_setValue("updated_134693", this.time + ""); this.call(false, true) } GM_registerMenuCommand(this.name + ": Check for updates", function () { GM_setValue("updated_134693", new Date().getTime() + ""); AutoUpdater_134693.call(true, true) }) } } }; AutoUpdater_134693.check(); 59 | } 60 | 61 | 62 | // Source: http://wiki.greasespot.net/Content_Script_Injection 63 | function contentEval(source) { 64 | // Check for function input. 65 | if ("function" == typeof (source)) { 66 | source = "(" + source + ")();"; 67 | } 68 | 69 | // Create a script node holding this source code. 70 | var script = document.createElement("script"); 71 | script.setAttribute("type", "application/javascript"); 72 | script.textContent = source; 73 | 74 | // Insert the script node into the page, so it will run, and immediately 75 | // remove it to clean up. 76 | document.body.appendChild(script); 77 | document.body.removeChild(script); 78 | } 79 | 80 | 81 | contentEval(function () { 82 | 83 | // Okay to run? 84 | if ("object" != typeof quakelive) { 85 | return; 86 | } 87 | 88 | 89 | var qlegh = {}; 90 | 91 | 92 | /** 93 | * Order by game time (newest first) 94 | */ 95 | qlegh.gtSort = function (a, b) { 96 | return parseInt(b.GAME_TIME) - parseInt(a.GAME_TIME); 97 | } 98 | 99 | 100 | /** 101 | * Determine whether mod_profile.InitGraphs will handle the update 102 | */ 103 | qlegh.okayToUpdate = function () { 104 | return ("profile" != quakelive.pathParts[0] && "statistics" != quakelive.pathParts[1]); 105 | } 106 | 107 | 108 | /** 109 | * Get fresh match data 110 | */ 111 | qlegh.getMatchHistory = function () { 112 | if (!qlegh.okayToUpdate()) { 113 | return; 114 | } 115 | $.ajax({ 116 | type: "GET", 117 | url: "/profile/statistics/" + quakelive.username 118 | }).done(function (data) { 119 | qlegh.syncMatchHistory($(data).find("#prf_matchjson").text(), true); 120 | }); 121 | } 122 | 123 | 124 | // Update on the initial load (delayed 5 seconds) 125 | setTimeout(function () { 126 | if (qlegh.okayToUpdate()) { 127 | qlegh.getMatchHistory(); 128 | } 129 | }, 5E3); 130 | 131 | // Update whenever the game exits 132 | quakelive.AddHook("OnGameExited", function () { 133 | if (!qlegh.okayToUpdate()) { 134 | return; 135 | } 136 | qlegh.getMatchHistory(); 137 | }); 138 | 139 | 140 | /** 141 | * Sync up history and available stats 142 | * @param {String} stats The JSON string for the array of match objects 143 | * @param {Boolean} auto Whether the call was initiated by the script 144 | */ 145 | qlegh.syncMatchHistory = function (stats, auto) { 146 | var history, newStats = []; 147 | auto = auto || false; 148 | 149 | try { 150 | stats = JSON.parse(stats); 151 | } 152 | catch (e) { 153 | // If called by InitGraphs... 154 | if (!auto) { 155 | oldInitGraphs.call(quakelive.mod_profile); 156 | } 157 | return; 158 | } 159 | 160 | // Get old stats 161 | history = amplify.store("qlegh_matchHistory") || []; 162 | 163 | // If we have no history, just use the available stats 164 | if (0 == history.length) { 165 | history = stats; 166 | } 167 | 168 | // Sanity check... newest games first 169 | history.sort(qlegh.gtSort); 170 | stats.sort(qlegh.gtSort); 171 | 172 | // Prepend newer stats to the history 173 | for (var i = 0, e = stats.length, h0 = parseInt(history[0].GAME_TIME) ; i < e; ++i) { 174 | if (parseInt(stats[i].GAME_TIME) > h0) { 175 | newStats.push(stats[i]); 176 | } 177 | else { 178 | break; 179 | } 180 | } 181 | history = newStats.reverse().concat(history); 182 | 183 | // Another sanity check... no duplicates 184 | var times = {}; 185 | for (var i = 0, e = history.length; i < e; ++i) { 186 | if (!(history[i].GAME_TIME in times)) { 187 | times[history[i].GAME_TIME] = true; 188 | } 189 | else { 190 | history.splice(i, 1); 191 | e--; 192 | i--; 193 | } 194 | } 195 | 196 | // Only keep the 50 most recent matches (and sort them again) 197 | // NOTE: Over 50 would be possible, but the bars get quite thin. 198 | history = history.splice(0, Math.min(50, history.length)).sort(qlegh.gtSort); 199 | amplify.store("qlegh_matchHistory", history); 200 | 201 | // If called by InitGraphs... 202 | if (!auto) { 203 | // Save the result back to a JSON string 204 | $("#prf_matchjson").text(JSON.stringify(history)); 205 | 206 | // Call the original function to initialize the graphs 207 | oldInitGraphs.call(quakelive.mod_profile); 208 | } 209 | } 210 | 211 | 212 | /** 213 | * Override InitGraphs to add the available match data to the history 214 | */ 215 | var oldInitGraphs = quakelive.mod_profile.InitGraphs; 216 | quakelive.mod_profile.InitGraphs = function () { 217 | // Only handle the user's own profile 218 | if ($("#prf_player_name").text().toLowerCase() != quakelive.username.toLowerCase()) { 219 | oldInitGraphs.call(quakelive.mod_profile); 220 | return; 221 | } 222 | 223 | qlegh.syncMatchHistory($("#prf_matchjson").text()); 224 | }; 225 | 226 | }); // end of call to contentEval 227 | -------------------------------------------------------------------------------- /source/scripts/attic/gametype.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Quake Live Game Type Picker 3 | // @version 1.3 4 | // @author PredatH0r 5 | // @description Select game-type and other server filters without opening the customization area 6 | // @include http://*.quakelive.com/* 7 | // @exclude http://*.quakelive.com/forum* 8 | // @unwrap 9 | // ==/UserScript== 10 | 11 | /* 12 | This script replaces the large "Public Matches" caption on the server browser page 13 | with quick customization items for gametype and match visibility 14 | 15 | Version 1.3 16 | - updated extraQL script url to sourceforge 17 | 18 | Version 1.1 19 | - added "Show filter panel" link 20 | */ 21 | 22 | (function() { 23 | // external variables 24 | var quakelive = window.quakelive; 25 | var extraQL = window.extraQL; 26 | 27 | var _GAME_TYPES = [ 28 | ["any", "any", 'tdm', 'All', 'All Game Types'], 29 | [2, 8, 'ffa', 'FFA', 'Free For All'], 30 | [4, 14, 'ca', 'CA', 'Clan Arena'], 31 | [7, 7, 'duel', 'Duel', 'One On One'], 32 | [6, 11, 'tdm', 'TDM', 'Team Deathmatch'], 33 | [3, 9, 'ctf', 'CTF', 'Capture The Flag'], 34 | [5, 10, 'ft', 'FT', 'Freeze Tag'], 35 | [16, 21, 'fctf', '1CTF', '1-Flag CTF'], 36 | [18, 23, 'ad', 'A&D', 'Attack & Defend'], 37 | [15, 20, 'dom', 'DOM', 'Domination'], 38 | [17, 22, 'harvester', 'HAR', 'Harvester'], 39 | [19, 24, 'rr', 'RR', 'Red Rover'], 40 | [25, 25, 'race', 'Race', 'Race Mode'] 41 | ]; 42 | var gameTypes = []; // transformed version of _GAME_TYPES: array of { index, regular, instagib, icon, text, hint } 43 | 44 | var currentGameTypeIndex = 0; 45 | var isInstagib = 0; 46 | 47 | function init() { 48 | if (extraQL.isOldUi) { 49 | extraQL.log("Sorry, but gametype.js is not compatible with this version of the QL UI."); 50 | return; 51 | } 52 | 53 | $.each(_GAME_TYPES, function(i, gameType) { 54 | gameTypes.push({ index: i, regular: gameType[0], instagib: gameType[1], icon: gameType[2], text: gameType[3], hint: gameType[4] }); 55 | }); 56 | extraQL.addStyle( 57 | "#gameTypeSwitcher { color: black; margin-left: 12px; font-family: Arial; display: inline-block; }", 58 | "#gameTypeSwitcher1 .gametype { display: inline-block; margin-right: 16px; }", 59 | "#gameTypeSwitcher1 a { color: inherit; text-decoration: none; }", 60 | "#gameTypeSwitcher1 a.active { color: #CC220B; text-decoration: underline; }", 61 | "#gameTypeSwitcher1 img { float: left; margin-right: 3px; width: 16px; height: 16px; }", 62 | "#gameTypeSwitcher1 span { vertical-align: middle; }", 63 | "#gameTypeSwitcher2 { margin: 3px 0; color: black; }", 64 | "#gameTypeSwitcher2 input { vertical-align: middle; }", 65 | "#gameTypeSwitcher2 label { margin: 0 20px 0 3px; vertical-align: middle; }", 66 | "#gameTypeSwitcher2 a { margin-left: 100px; color: black; text-decoration: underline; cursor: pointer; }", 67 | "#quickPublic { margin-left: 50px; }" 68 | ); 69 | quakelive.AddHook("OnContentLoaded", onContentLoaded); 70 | } 71 | 72 | function onContentLoaded() { 73 | if (!quakelive.activeModule || quakelive.activeModule.GetTitle() != "Home") 74 | return; 75 | 76 | var $matchlistHeader = $("#matchlist_header_text"); 77 | if ($matchlistHeader.length == 0) return; 78 | $matchlistHeader.empty(); 79 | $matchlistHeader.css({ "display": "none", "height": "auto" }); 80 | 81 | $matchlistHeader = $("#matchlist_header"); 82 | 83 | var html1 = ""; 84 | $.each(gameTypes, function(i, gameType) { 85 | html1 += ""; 87 | }); 88 | var html2 = "" 89 | + "" 90 | + "" 91 | + "" 92 | + "" 93 | + ""; 94 | $matchlistHeader.prepend( 95 | "
    " 96 | + "
    " + html1 + "
    " 97 | + "
    " + html2 + "
    " 98 | +"
    " 99 | ); 100 | $("#matchlist_header_controls").css({ "width": "auto", "float": "right" }); 101 | 102 | $("#gameTypeSwitcher1 a").click(function () { 103 | currentGameTypeIndex = $(this).data("index"); 104 | updateCustomizationFormGameType(); 105 | }); 106 | 107 | $("#quickInsta").change(function() { 108 | isInstagib = $(this).prop("checked"); 109 | updateCustomizationFormGameType(); 110 | }); 111 | 112 | $("#quickFriends").change(function () { 113 | $("#ctrl_filter_social").val($("#quickFriends").prop("checked") ? "friends" : "any").trigger("chosen:updated").trigger("change"); 114 | }); 115 | 116 | $("#quickPrem").change(function () { 117 | $("#premium_only").prop("checked", $("#quickPrem").prop("checked")).trigger("change"); 118 | }); 119 | 120 | $("#gameTypeSwitcher2 input:radio").click(function () { 121 | var privateValue = $(this).prop("value"); 122 | $("#publicServer").parent().find("input:radio[value=" + privateValue + "]").prop("checked", "true").trigger("click"); 123 | }); 124 | 125 | parseCustomizationForm(); 126 | } 127 | 128 | function parseCustomizationForm() { 129 | var $field = $("#ctrl_filter_gametype"); 130 | var currentGameTypeValue = $field.val(); 131 | 132 | currentGameTypeIndex = -1; 133 | isInstagib = false; 134 | $.each(gameTypes, function (i, gameType) { 135 | if (gameType.regular == currentGameTypeValue || gameType.instagib == currentGameTypeValue) { 136 | currentGameTypeIndex = i; 137 | isInstagib = currentGameTypeValue != gameType.regular; 138 | } 139 | }); 140 | $("#quickPrem").prop("checked", $("#premium_only").prop("checked")); 141 | var privateValue = $("#publicServer").parent().find("input:radio:checked").val(); 142 | $("#gameTypeSwitcher2").find("input:radio[value=" + privateValue + "]").prop("checked", "true"); 143 | highlightActiveGametype(); 144 | } 145 | 146 | function updateCustomizationFormGameType() { 147 | if (currentGameTypeIndex < 0) return; 148 | var gameType = gameTypes[currentGameTypeIndex]; 149 | $("#ctrl_filter_gametype").val(isInstagib ? gameType.instagib : gameType.regular).trigger("chosen:updated").trigger("change"); 150 | highlightActiveGametype(); 151 | } 152 | 153 | function highlightActiveGametype() { 154 | $("#gameTypeSwitcher1 .gametype .active").removeClass("active"); 155 | $("#gameTypeSwitcher1 .gametype [data-index=" + currentGameTypeIndex + "]").addClass("active"); 156 | var gameType = currentGameTypeIndex >= 0 ? gameTypes[currentGameTypeIndex] : null; 157 | var allowInsta = gameType && gameType.instagib != gameType.regular; 158 | $("#quickInsta").prop("disabled", !allowInsta); 159 | $("#quickInsta").prop("checked", allowInsta && isInstagib); 160 | } 161 | 162 | init(); 163 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/irc.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 186820 3 | // @name Quake Live IRC Link 4 | // @version 1.3 5 | // @author PredatH0r 6 | // @description Adds a link to the QuakeNet.org IRC Web Chat to your chat window 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /* 11 | 12 | Version 1.3 13 | - added workaround for external links in QL Steam build 14 | 15 | Version 1.2 16 | - ensuring consistent order of tabs in the chat bar 17 | 18 | Version 1.1 19 | - updated extraQL script url to sourceforge 20 | 21 | */ 22 | 23 | (function () { 24 | // external global variables 25 | var quakelive = window.quakelive; 26 | var extraQL = window.extraQL; 27 | 28 | var URL_IRC = "http://webchat.quakenet.org/?nick=" + quakelive.username + "&channels=quakelive&prompt=1"; 29 | 30 | function init() { 31 | // delay init so that twitch, twitter, ESR and IRC scripts add items to chat menu bar in a defined order 32 | if (extraQL.hookVersion) // introduced at the same time as the addTabPage() "priority" param 33 | delayedInit(); 34 | else 35 | setTimeout(delayedInit, 2400); 36 | } 37 | 38 | function delayedInit() { 39 | onContentLoaded(); 40 | //extraQL.addStyle("#tab_irc { float: right; }"); 41 | quakelive.AddHook("OnContentLoaded", onContentLoaded); 42 | } 43 | 44 | function onContentLoaded() { 45 | if ($("#tab_irc").length) 46 | return; 47 | extraQL.addTabPage("irc", "IRC", "", function() { 48 | quakelive.OpenURL(URL_IRC); 49 | window.event.stopPropagation(); 50 | }, 400); 51 | } 52 | 53 | init(); 54 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/joinGame.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id joinGame 3 | // @name Join Game Through HTTP Link 4 | // @version 1.0 5 | // @author PredatH0r 6 | // @description Joins a game through a link of the format http://127.0.0.1:27963/join/91.198.152.211:27003/passwd 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | (function () { 11 | var hInterval; 12 | 13 | function init() { 14 | if (!extraQL || !extraQL.isLocalServerRunning()) 15 | return; 16 | 17 | quakelive.AddHook("OnGameModeEnded", startPolling); 18 | startPolling(); 19 | } 20 | 21 | function startPolling() { 22 | hInterval = window.setInterval(pollJoinInformation, 1000); 23 | } 24 | 25 | function pollJoinInformation() { 26 | $.getJSON(extraQL.BASE_URL + "join", function(info) { 27 | if (info.server) { 28 | window.clearTimeout(hInterval); 29 | if (info.pass) 30 | quakelive.cvars.Set("password", info.pass); 31 | window.join_server(info.server, undefined, info.pass); 32 | } 33 | }); 34 | } 35 | 36 | init(); 37 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/keyboardNav.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 186881 3 | // @name Quake Live Keyboard Navigation 4 | // @version 0.16 5 | // @namespace phob.net 6 | // @author wn 7 | // @description Adds some browser navigation functionality to the QUAKE LIVE standalone client 8 | // @include http://*.quakelive.com/* 9 | // @exclude http://*.quakelive.com/forum* 10 | // @run-at document-end 11 | // @updateURL https://userscripts.org/scripts/source/186881.meta.js 12 | // ==/UserScript== 13 | 14 | //////////////////////////////////////////////////////////////////////////////////////////////////// 15 | // DEPENDENCIES 16 | //////////////////////////////////////////////////////////////////////////////////////////////////// 17 | 18 | /* mousetrap v1.4.6 craig.is/killing/mice */ 19 | !("Mousetrap" in window) && (function (J, r, f) { 20 | function s(a, b, d) { a.addEventListener ? a.addEventListener(b, d, !1) : a.attachEvent("on" + b, d) } function A(a) { if ("keypress" == a.type) { var b = String.fromCharCode(a.which); a.shiftKey || (b = b.toLowerCase()); return b } return h[a.which] ? h[a.which] : B[a.which] ? B[a.which] : String.fromCharCode(a.which).toLowerCase() } function t(a) { a = a || {}; var b = !1, d; for (d in n) a[d] ? b = !0 : n[d] = 0; b || (u = !1) } function C(a, b, d, c, e, v) { 21 | var g, k, f = [], h = d.type; if (!l[a]) return []; "keyup" == h && w(a) && (b = [a]); for (g = 0; g < l[a].length; ++g) if (k = 22 | l[a][g], !(!c && k.seq && n[k.seq] != k.level || h != k.action || ("keypress" != h || d.metaKey || d.ctrlKey) && b.sort().join(",") !== k.modifiers.sort().join(","))) { var m = c && k.seq == c && k.level == v; (!c && k.combo == e || m) && l[a].splice(g, 1); f.push(k) } return f 23 | } function K(a) { var b = []; a.shiftKey && b.push("shift"); a.altKey && b.push("alt"); a.ctrlKey && b.push("ctrl"); a.metaKey && b.push("meta"); return b } function x(a, b, d, c) { 24 | m.stopCallback(b, b.target || b.srcElement, d, c) || !1 !== a(b, d) || (b.preventDefault ? b.preventDefault() : b.returnValue = !1, b.stopPropagation ? 25 | b.stopPropagation() : b.cancelBubble = !0) 26 | } function y(a) { "number" !== typeof a.which && (a.which = a.keyCode); var b = A(a); b && ("keyup" == a.type && z === b ? z = !1 : m.handleKey(b, K(a), a)) } function w(a) { return "shift" == a || "ctrl" == a || "alt" == a || "meta" == a } function L(a, b, d, c) { function e(b) { return function () { u = b; ++n[a]; clearTimeout(D); D = setTimeout(t, 1E3) } } function v(b) { x(d, b, a); "keyup" !== c && (z = A(b)); setTimeout(t, 10) } for (var g = n[a] = 0; g < b.length; ++g) { var f = g + 1 === b.length ? v : e(c || E(b[g + 1]).action); F(b[g], f, c, a, g) } } function E(a, b) { 27 | var d, 28 | c, e, f = []; d = "+" === a ? ["+"] : a.split("+"); for (e = 0; e < d.length; ++e) c = d[e], G[c] && (c = G[c]), b && "keypress" != b && H[c] && (c = H[c], f.push("shift")), w(c) && f.push(c); d = c; e = b; if (!e) { if (!p) { p = {}; for (var g in h) 95 < g && 112 > g || h.hasOwnProperty(g) && (p[h[g]] = g) } e = p[d] ? "keydown" : "keypress" } "keypress" == e && f.length && (e = "keydown"); return { key: c, modifiers: f, action: e } 29 | } function F(a, b, d, c, e) { 30 | q[a + ":" + d] = b; a = a.replace(/\s+/g, " "); var f = a.split(" "); 1 < f.length ? L(a, f, b, d) : (d = E(a, d), l[d.key] = l[d.key] || [], C(d.key, d.modifiers, { type: d.action }, 31 | c, a, e), l[d.key][c ? "unshift" : "push"]({ callback: b, modifiers: d.modifiers, action: d.action, seq: c, level: e, combo: a })) 32 | } var h = { 8: "backspace", 9: "tab", 13: "enter", 16: "shift", 17: "ctrl", 18: "alt", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "left", 38: "up", 39: "right", 40: "down", 45: "ins", 46: "del", 91: "meta", 93: "meta", 224: "meta" }, B = { 106: "*", 107: "+", 109: "-", 110: ".", 111: "/", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'" }, H = { 33 | "~": "`", "!": "1", 34 | "@": "2", "#": "3", $: "4", "%": "5", "^": "6", "&": "7", "*": "8", "(": "9", ")": "0", _: "-", "+": "=", ":": ";", '"': "'", "<": ",", ">": ".", "?": "/", "|": "\\" 35 | }, G = { option: "alt", command: "meta", "return": "enter", escape: "esc", mod: /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? "meta" : "ctrl" }, p, l = {}, q = {}, n = {}, D, z = !1, I = !1, u = !1; for (f = 1; 20 > f; ++f) h[111 + f] = "f" + f; for (f = 0; 9 >= f; ++f) h[f + 96] = f; s(r, "keypress", y); s(r, "keydown", y); s(r, "keyup", y); var m = { 36 | bind: function (a, b, d) { a = a instanceof Array ? a : [a]; for (var c = 0; c < a.length; ++c) F(a[c], b, d); return this }, 37 | unbind: function (a, b) { return m.bind(a, function () { }, b) }, trigger: function (a, b) { if (q[a + ":" + b]) q[a + ":" + b]({}, a); return this }, reset: function () { l = {}; q = {}; return this }, stopCallback: function (a, b) { return -1 < (" " + b.className + " ").indexOf(" mousetrap ") ? !1 : "INPUT" == b.tagName || "SELECT" == b.tagName || "TEXTAREA" == b.tagName || b.isContentEditable }, handleKey: function (a, b, d) { 38 | var c = C(a, b, d), e; b = {}; var f = 0, g = !1; for (e = 0; e < c.length; ++e) c[e].seq && (f = Math.max(f, c[e].level)); for (e = 0; e < c.length; ++e) c[e].seq ? c[e].level == f && (g = !0, 39 | b[c[e].seq] = 1, x(c[e].callback, d, c[e].combo, c[e].seq)) : g || x(c[e].callback, d, c[e].combo); c = "keypress" == d.type && I; d.type != u || w(a) || c || t(b); I = g && "keydown" == d.type 40 | } 41 | }; J.Mousetrap = m; "function" === typeof define && define.amd && define(m) 42 | })(window, document); 43 | 44 | 45 | //////////////////////////////////////////////////////////////////////////////////////////////////// 46 | // BIND KEY COMBOS 47 | //////////////////////////////////////////////////////////////////////////////////////////////////// 48 | 49 | // Pulled from http://www.codinghorror.com/blog/2006/02/standard-browser-keyboard-shortcuts.html 50 | 51 | // Home 52 | Mousetrap.bind("alt+home", function () { quakelive.Goto("home"); }); 53 | 54 | // Back 55 | Mousetrap.bind(["alt+left", "backspace"], function () { history.back(); }); 56 | 57 | // Forward 58 | Mousetrap.bind(["alt+right", "shift+backspace"], function () { history.forward(); }); 59 | 60 | // Refresh 61 | Mousetrap.bind("f5", function () { location.reload(); }); 62 | 63 | // Refresh with cache purging (TODO: test if it works...) 64 | Mousetrap.bind("mod+f5", function () { location.reload(true); }); 65 | 66 | // URL navigation 67 | Mousetrap.bind("mod+l", function () { 68 | function escapeHandler(e) { if (27 === e.keyCode) cleanup(); } 69 | function cleanup() { 70 | $("#modal-input > input")[0].blur(); 71 | $("#navprompt").jqmHide(); 72 | $("body").off("keyup", escapeHandler); 73 | } 74 | 75 | qlPrompt({ 76 | id: "navprompt" 77 | , title: "Navigate" 78 | , body: "Warning: A non-QUAKE LIVE URL will open your default browser." 79 | , showX: true 80 | , input: true 81 | , inputLabel: location.href 82 | , ok: function () { 83 | var url = $("#modal-input > input").val().trim(); 84 | // If a QL URL, just navigate directly... 85 | if (/^https?:\/\/\w+\.quakelive\.com/.test(url)) { 86 | location.href = url; 87 | } 88 | // ... otherwise trigger opening in a new window 89 | else { 90 | if (0 !== url.indexOf("http:") && 0 !== url.indexOf("https:")) url = "http://" + url; 91 | window.open(url, "_blank"); 92 | } 93 | cleanup(); 94 | } 95 | , cancel: cleanup 96 | }); 97 | 98 | setTimeout(function () { 99 | $("#modal-input > input")[0].select(); 100 | $("body").on("keyup", escapeHandler); 101 | }, 0); 102 | }); 103 | -------------------------------------------------------------------------------- /source/scripts/attic/linkify.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 187258 3 | // @name Linkify 4 | // @version 1.6 5 | // @author PredatH0r 6 | // @description Turn plain text URLs into links 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /******************************************************************************* 11 | 12 | Linkify Plus modified by PredatH0r, based on modified version from kry. 13 | This version is tailored to work with Quake Live standalone client and QLHM. 14 | 15 | Version 1.6 16 | - fixed workaround to open internal QL URLs in QL browser insteam of system browser 17 | 18 | Version 1.5 19 | - added workaround to open external links in QL Steam build 20 | 21 | Version 1.4 22 | - fixed cannot call getElementById of undefined 23 | 24 | Version 1.3 25 | - compatibility with redesigned QL UI 26 | - removed code for color selection 27 | 28 | Version 1.2 29 | - fixed link detection in chat after receiving a couple messages 30 | 31 | Version 1.1 32 | - now also handles links in chat messages from non-active chats 33 | 34 | Version 1.0 35 | - fixed drop down list for color selection 36 | - replaced DOM node event listeners with Quake Live specific handlers 37 | 38 | 39 | Linkifier Plus modified by kry. Licensed for unlimited modification 40 | and redistribution as long as this notice is kept intact. 41 | 42 | Only modified Linkify Plus to change the link color to red because 43 | in Quake Live the links appeared in white with white background. 44 | Everything else is written by Anthony Liuallen of http://arantius.com/ 45 | 46 | Also I have made minor bugfixes to make it work better in Quake Live. 47 | 48 | Link to Linkify Plus 49 | http://userscripts.org/scripts/show/1352 50 | 51 | ** Linkify plus comments below 52 | Loosely based on the Linkify script located at: 53 | http://downloads.mozdev.org/greasemonkey/linkify.user.js 54 | 55 | Originally written by Anthony Lieuallen of http://arantius.com/ 56 | Licensed for unlimited modification and redistribution as long as 57 | this notice is kept intact. 58 | 59 | If possible, please contact me regarding new features, bugfixes 60 | or changes that I could integrate into the existing code instead of 61 | creating a different script. Thank you 62 | 63 | Linkify Plus Red Links Version history: 64 | Version 1.1.9 65 | - Replaced /r/ from the links with /#! - if someone gave you a join link or a link to game stats before it could have reloaded QL again but this prevents it 66 | Version 1.1.8 67 | - Compatibility fix - had UI problems with custom map launcher 68 | Version 1.1.7 69 | - Fixed last version and other small fixes 70 | Version 1.1.6 71 | - Changed where links are opened. All Quake Live links that are not to forums are opened in the same window and the rest will open in a new tab or window. 72 | Version 1.1.5 73 | - Rewrote functions that gave the text to linkifycontainer, now the script should be a lot lighter 74 | Version 1.1.4 75 | - Another bugfix and stopped looking for email addresses. 76 | Version 1.1.3 77 | - Bugfix. 78 | Version 1.1.2 79 | - Removed commands from Greasemonkey "User Script Commands..." menu and added a dropdown selection to change color for better compatibility. 80 | Version 1.1.1 81 | - Still better lagfix. 82 | Version 1.1.0 83 | - Even better lagfix. 84 | Version 1.0.9 85 | - Attemp to fix lag. 86 | Version 1.0.8 87 | - Little improvement to lagfix. 88 | Version 1.0.7 89 | - Improved lagfix. 90 | Version 1.0.6 91 | - Lagfix. 92 | Version 1.0.5 93 | - Made the color changing work better. 94 | Version 1.0.4 95 | - Possibility to change color through Greasemonkey "User Script Commands..." menu. 96 | Version 1.0.3 97 | - Limited the use to Quake Live. 98 | Version 1.0.2 99 | - Changed event listener type. Sometimes a node was not inserted, but modified. 100 | Version 1.0.1 101 | - Parenthesis fix. 102 | Version 1.0.0 103 | - Changed link color to red. 104 | 105 | Using the Linkify Plus version 2.0.2. as a base. 106 | 107 | *******************************************************************************/ 108 | 109 | (function() { 110 | var document = window.document; 111 | var quakelive = window.quakelive; 112 | 113 | var notInTags = [ 'a', 'head', 'noscript', 'option', 'script', 'style', 'title', 'textarea' ]; 114 | var textNodeXpath = ".//text()[not(ancestor::" + notInTags.join(') and not(ancestor::') + ")]"; 115 | var urlRE = /((?:https?|ftp):\/\/[^\s'"'<>]+|www\.[^\s'"'<>]+)/gi; 116 | 117 | var linkcolor = "#CA3827"; 118 | 119 | /******************************************************************************/ 120 | var oldSelectContact; 121 | var inOnMessage = false; 122 | 123 | function init() { 124 | quakelive.AddOnceHook("OnLayoutLoaded", onLayoutLoaded); 125 | } 126 | 127 | function onLayoutLoaded() { 128 | // handle incoming chat messages 129 | quakelive.AddHook('IM_OnMessage', IM_OnMessage); 130 | oldSelectContact = quakelive.mod_friends.roster.SelectContact; 131 | quakelive.mod_friends.roster.SelectContact = SelectContact; 132 | 133 | // handle content changes 134 | quakelive.AddHook("OnContentLoaded", replaceLinks); 135 | } 136 | 137 | /******************************************************************************/ 138 | 139 | function SelectContact(contact) { 140 | try { 141 | oldSelectContact.call(quakelive.mod_friends.roster, contact); 142 | var node = document.getElementById("im-chat-body"); 143 | linkifyContainer(node); 144 | } catch (ex) {} 145 | } 146 | 147 | function IM_OnMessage() { 148 | try { 149 | if (inOnMessage) 150 | return; 151 | inOnMessage = true; 152 | 153 | // need to delay this action a bit because the message has not yet been added to the chat window 154 | window.setTimeout(function() { 155 | var node = document.getElementById("im-chat-body"); 156 | if (node) 157 | linkifyContainer(node); 158 | }, 300); 159 | } 160 | catch(e) { 161 | } 162 | finally { 163 | inOnMessage = false; 164 | } 165 | } 166 | 167 | function replaceLinks() { 168 | if (document.getElementById("qlv_profileTopLeft")) 169 | linkifyContainer(document.getElementById("qlv_profileTopLeft")); 170 | } 171 | 172 | function linkifyContainer(container) { 173 | var xpathResult = document.evaluate( 174 | textNodeXpath, container, null, 175 | XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null 176 | ); 177 | 178 | var i = 0; 179 | 180 | function continuation() { 181 | var node, limit = 0; 182 | while (node = xpathResult.snapshotItem(i++)) { 183 | linkifyTextNode(node); 184 | 185 | if (++limit > 50) { 186 | return setTimeout(continuation, 0); 187 | } 188 | } 189 | } 190 | 191 | setTimeout(continuation, 0); 192 | } 193 | 194 | function linkifyTextNode(node) { 195 | var l, m; 196 | var txt = node.textContent; 197 | var span = null; 198 | var p = 0; 199 | 200 | while (m = urlRE.exec(txt)) { 201 | 202 | if (null == span) { 203 | //create a span to hold the new text with links in it 204 | span = document.createElement('span'); 205 | } 206 | 207 | //get the link without trailing dots 208 | l = m[0].replace(/\.*$/, ''); 209 | l = l.replace('/r/', '/#!'); 210 | //put in text up to the link 211 | span.appendChild(document.createTextNode(txt.substring(p, m.index))); 212 | //create a link and put it in the span 213 | a = document.createElement('a'); 214 | a.className = 'linkifyplus'; 215 | a.appendChild(document.createTextNode(l)); 216 | if (l.match(/^www/i)) { 217 | l = 'http://' + l; 218 | } else if (-1 == l.indexOf('://')) { 219 | l = 'mailto:' + l; 220 | } 221 | if (!l.match("\\.quakelive\\.com/#")) 222 | l = "javascript:quakelive.OpenURL(\"" + l + "\")"; 223 | a.setAttribute('href', l); 224 | a.style.color = linkcolor; 225 | if (l.match(/^http:\/\/www\.quakelive\.com\/forum/i) || !(l.match(/^http:\/\/www\.quakelive\.com/i) || l.match(/^https:\/\/secure\.quakelive\.com/i))) { 226 | a.setAttribute('target', '_blank'); 227 | } 228 | span.appendChild(a); 229 | //track insertion point 230 | p = m.index + m[0].length; 231 | } 232 | if (span) { 233 | //take the text after the last link 234 | span.appendChild(document.createTextNode(txt.substring(p, txt.length))); 235 | //replace the original text with the new span 236 | try { 237 | if (node && node.parentNode) 238 | node.parentNode.replaceChild(span, node); 239 | } catch(e) { 240 | console.error(e); 241 | console.log(node); 242 | } 243 | } 244 | } 245 | 246 | try { 247 | init(); 248 | } catch (e) { 249 | console.log(e); 250 | } 251 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/links.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id links 3 | // @name Links to QL related Websites 4 | // @version 6 5 | // @author PredatH0r 6 | // @description Adds a "Links" menu with links to important QL related websites 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /* 11 | 12 | Version 6 13 | - replaced link for "Duel Spawn Logic" 14 | 15 | Version 5.0 16 | - added http://qlmm.gameboni.com/ 17 | 18 | Version 4.0 19 | - added http://qlhud.net/ 20 | 21 | Version 3.0 22 | - added some links 23 | 24 | Version 2.0 25 | - added some links 26 | - added separator lines to group links 27 | 28 | Version 1.0 29 | - first public release 30 | 31 | */ 32 | 33 | (function () { 34 | var menuCaption = "Links"; 35 | 36 | extraQL.addStyle("ul.sf-menu li ul li.sep { border-top: 1px solid #888; }"); 37 | 38 | function init() { 39 | nav.navbar[menuCaption] = { 40 | id: "eql_links", 41 | callback: "", 42 | submenu: { 43 | "Quakenet IRChat": { newwindow: "http://webchat.quakenet.org/?nick=" + quakelive.username + "&channels=quakelive&prompt=1" }, 44 | "ESReality Forum": { newwindow: "http://www.esreality.com/?a=post&forum=17" }, 45 | "Reddit Forum": { newwindow: "http://www.reddit.com/r/quakelive"}, 46 | "Quakehub Videos": { newwindow: "http://quakehub.com/" }, 47 | "Quake History": { newwindow: "http://www.quakehistory.com/" }, 48 | "QLRanks": { newwindow: "http://www.qlranks.com/", "class": "sep" }, 49 | "QLStats": { newwindow: "http://ql.leeto.fi/#/" }, 50 | "Duel Match Maker": { newwindow: "http://qlmm.gameboni.com/" }, 51 | "FaceIt Cups": { newwindow: "http://play.faceit.com/" }, 52 | "125fps League": { newwindow: "https://twitter.com/125fps" }, 53 | "vHUD Editor": { newwindow: "http://visualhud.pk69.com/", "class": "sep" }, 54 | "qlhud Custom HUDs": { newwindow: "http://qlhud.net/"}, 55 | "Wolfcam/Whisperer": { newwindow: "http://www.wolfwhisperer.net/"}, 56 | "Yakumo's QL Guide": { newwindow: "http://www.quakelive.com/forum/showthread.php?831-The-Ultimate-Quake-Live-Guide", "class": "sep" }, 57 | "QL Console Guide": { newwindow: "http://www.regurge.at/ql/" }, 58 | "Strafing Theory": { newwindow: "http://www.funender.com/quake/articles/strafing_theory.html" }, 59 | "Duel Spawn Logic": { newwindow: "http://www.esreality.com/wiki/Quake_Live:_Strategy:_Duel:_Spawn_Control" } 60 | } 61 | }; 62 | } 63 | 64 | init(); 65 | 66 | })(window); -------------------------------------------------------------------------------- /source/scripts/attic/matchtip.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 186527 3 | // @name Quake Live Left-Hand-Side Playerlist Popup 4 | // @version 1.6 5 | // @description Displays a QuakeLive server's player list on the left side of the cursor 6 | // @author PredatH0r 7 | // @include http://*.quakelive.com/* 8 | // @exclude http://*.quakelive.com/forum* 9 | // @unwrap 10 | // ==/UserScript== 11 | 12 | /* 13 | 14 | Version 1.6 15 | - fixed interference with server browser details no longer scrolling 16 | - code cleanup 17 | 18 | Version 1.5 19 | - compatibility with "join URL" and fixed location "chat" 20 | 21 | Version 1.4 22 | - fixed clipping of tool tip with low y-coord and flicker when scrolling window 23 | 24 | Version 1.3 25 | - moved popup up so that current server browser like is fully visible 26 | 27 | Version 1.2: 28 | - added try/catch 29 | - fixed timing gap between read/write of DisplayMatchPlayers 30 | 31 | */ 32 | 33 | (function (win) { // limit scope of variables and functions 34 | 35 | var quakelive = win.quakelive; 36 | var oldShowTooltip; 37 | var oldDisplayMatchPlayers; 38 | 39 | function init() { 40 | oldShowTooltip = quakelive.matchtip.ShowTooltip; 41 | oldDisplayMatchPlayers = quakelive.matchtip.DisplayMatchPlayers; 42 | 43 | quakelive.matchtip.ShowTooltip = function (tip, node, content, footer, showArrow) { 44 | this.reposnode = node; 45 | this.repostip = tip; 46 | this.reposshowArrow = showArrow; 47 | 48 | this.reposForChat = node.parents(".rosteritem").length > 0; 49 | 50 | var ret = oldShowTooltip.call(this, tip, node, content, footer, showArrow); 51 | this.RepositionTip(); 52 | return ret; 53 | } 54 | 55 | quakelive.matchtip.RepositionTip = function () { 56 | try { 57 | var ARROW_HEIGHT = 175; 58 | var ctx = quakelive.matchtip; 59 | var node = ctx.reposnode; 60 | var tip = ctx.repostip; 61 | var showArrow = ctx.reposshowArrow; 62 | 63 | var nodeCenter = node.offset().top + node.innerHeight() / 2; 64 | var deltaY = $(document).scrollTop(); 65 | 66 | var top, left, $arrow; 67 | if (ctx.reposForChat) { 68 | // position the tip window in chat area on left-hand side of the node 69 | left = node.offset().left; 70 | top = nodeCenter - tip.outerHeight() / 2 - deltaY; 71 | if (top < 0) top = 0; 72 | tip.css({ "position": "fixed", "left": (left - 21 - tip.outerWidth()) + "px", "top": top + "px" }); 73 | 74 | // position the arrow 75 | $("#lgi_arrow_left").remove(); 76 | $arrow = $("#lgi_arrow_right"); 77 | if (showArrow) { 78 | if ($arrow.length == 0) { 79 | $arrow = $("
    "); 80 | $("#lgi_srv_fill").append($arrow); 81 | } 82 | top = nodeCenter - ARROW_HEIGHT / 2 - deltaY; 83 | $arrow.css({ "position": "fixed", "left": (left - 25) + "px", "top": top + "px" }); 84 | } 85 | } 86 | else { 87 | // position the tip window in content area on right-hand side of the node 88 | left = node.offset().left + node.outerWidth() + 22; 89 | top = nodeCenter - 309 / 2 - deltaY; 90 | if (top < 0) top = 0; 91 | tip.css({ "position": "absolute", "left": left + "px", "top": top + "px" }); 92 | 93 | // position the arrow 94 | $("#lgi_arrow_right").remove(); 95 | if (showArrow) { 96 | $arrow = $("#lgi_arrow_left"); 97 | if ($arrow.length == 0) { 98 | $arrow = $("
    "); 99 | $("#lgi_srv_fill").append($arrow); 100 | } 101 | top = nodeCenter - ARROW_HEIGHT / 2 - deltaY - top; 102 | $arrow.css({ "position": "absolute", "left": "-21px", "top": top + "px" }); 103 | } 104 | } 105 | } catch (e) { 106 | console.log("^1" + e.stack + "^7"); 107 | } 108 | } 109 | 110 | quakelive.matchtip.DisplayMatchPlayers = function (server) { 111 | var ret = oldDisplayMatchPlayers.call(this, server); 112 | try { 113 | // add a close button to the player list window 114 | var closeBtn = $("#lgi_headcol_close"); 115 | if (closeBtn.length == 0) 116 | $("#lgi_cli_top").append('
    '); 117 | 118 | // position player list window 119 | var $tip = $('#lgi_tip'); 120 | var top = parseInt($tip.css("top")) + 3; 121 | var $cli = $("#lgi_cli"); 122 | if (this.reposForChat) 123 | $cli.css({ "position": "fixed", "left": ($tip.offset().left - $cli.outerWidth() + 4) + "px", "top": top + "px", "z-index": 22000 }); 124 | else { 125 | var left = $tip.offset().left + $tip.outerWidth(); 126 | $cli.css({ "position": "absolute", "left": left + "px", "right": "auto", "top": top + "px", "z-index": 22000 }); 127 | } 128 | } 129 | catch(e) {} 130 | return ret; 131 | }; 132 | } 133 | 134 | init(); 135 | })(window); -------------------------------------------------------------------------------- /source/scripts/attic/openChat.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id openChat 3 | // @name Auto-Open chat when starting QL 4 | // @description Opens the chat window when QL starts 5 | // @author PredatH0r 6 | // @version 1.0 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | (function () { 11 | extraQL.showTabPage("qlv_chatControl"); 12 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/playerStatus.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 187322 3 | // @name Quake Live Player Status 4 | // @version 1.2 5 | // @description Shows match information in your friend list 6 | // @author PredatH0r, rahzei 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /* 11 | This script is modified version of rahzei's http://userscripts.org/scripts/review/96328 version 0.4 12 | It is now compatible with the QL standalone launcher and QLHM 13 | 14 | Version 1.2 15 | - fixed "cannot read property game_type of undefined" error message, when there's an invalid game in the friend list 16 | 17 | Version 1.1 18 | - fixed "Uncaught TypeError: Cannot read property 'ADDRESS' of null" error messges 19 | 20 | */ 21 | 22 | (function (unsafeWindow) { // enforce a local scope for variables and functions 23 | 24 | var quakelive, $; 25 | 26 | var GameTypes = { 27 | 0: "FFA", 28 | 1: "Duel", 29 | 2: "Race", 30 | 3: "TDM", 31 | 4: "CA", 32 | 5: "CTF" 33 | }; 34 | 35 | var Locations = { 36 | 40: 'ARG', 37 | 14: 'AUS', 38 | 33: 'AUS', 39 | 35: 'AUS', 40 | 51: 'AUS', 41 | 26: 'CAN', 42 | 38: 'CHL', 43 | 18: 'DE', 44 | 28: 'ES', 45 | 20: 'FR', 46 | 19: 'UK', 47 | 41: 'ISL', 48 | 42: 'JPN', 49 | 49: 'KOR', 50 | 17: 'NL', 51 | 32: 'PL', 52 | 37: 'RO', 53 | 39: 'RO', 54 | 44: 'RU', 55 | 47: 'SRB', 56 | 29: 'SE', 57 | 58: 'UKR', 58 | 6: 'TX', 59 | 10: 'CA', 60 | 11: 'VA', 61 | 16: 'CA', 62 | 21: 'IL', 63 | 22: 'GA', 64 | 23: 'WA', 65 | 24: 'NY', 66 | 25: 'CA', 67 | 46: 'ZAF' 68 | }; 69 | 70 | function GM_addStyle(css) { 71 | $("head").append(""); 72 | } 73 | 74 | 75 | /* 76 | Copy from ql source, out of scope 77 | */ 78 | 79 | function JID(a) { 80 | this.bare = a; 81 | var d = a.indexOf("@"); 82 | if (d != -1) this.username = a.substring(0, d); 83 | else { 84 | this.username = a; 85 | this.bare = a + "@" + quakelive.siteConfig.xmppDomain 86 | } 87 | } 88 | 89 | JID.prototype.Clone = function () { 90 | return new JID(this.bare) 91 | }; 92 | 93 | /* 94 | Wait for playerlist to make it into the DOM 95 | */ 96 | 97 | function init() { 98 | try { 99 | // setup our globals 100 | $ = unsafeWindow.jQuery; 101 | quakelive = unsafeWindow.quakelive; 102 | 103 | // Loop timer till playerlist is complete 104 | if ($(".player_name").length == 0) { 105 | window.setTimeout(init, 1000); 106 | //console.log("Waiting on quakelive object init..."); 107 | return; 108 | } 109 | 110 | quakelive.AddHook("IM_OnConnected", init); 111 | 112 | // Override OnPresence() 113 | QuakeLiveChatExtender(); 114 | 115 | // Run initial update on player_name nodes 116 | initRoster(); 117 | } 118 | catch (e) { } 119 | } 120 | 121 | function QuakeLiveChatExtender() { 122 | 123 | // Override 124 | quakelive.xmppClient.roster.OnPresence = function (a) { 125 | try { 126 | if (a = quakelive.Eval(a)) { 127 | var d = new JID(a.who); 128 | if (typeof this.items[d.username] != "undefined") { 129 | d = this.items[d.username]; 130 | this.ChangeItemPresence(d, a.presence); 131 | d.status = a.status; 132 | var nick = a.who.substring(0, a.who.indexOf("@")); 133 | 134 | // update status message 135 | updateNode(nick, a.status); 136 | 137 | } 138 | } 139 | } 140 | catch (e) { } 141 | }; 142 | } 143 | 144 | /* 145 | Updates the rosteritem with the correct nick 146 | */ 147 | 148 | function updateNode(nick, status) { 149 | try { 150 | status = quakelive.Eval(status); 151 | $(".player_name").each(function (i, item) { 152 | var that = this; 153 | var prehtml = $(this).html(); 154 | var nodeNick = $(this).html().indexOf("") >= 0 ? $(this).html().substring(prehtml.indexOf("") + "".length).toLowerCase() : $(this).html().toLowerCase(); 155 | 156 | // FIXED: I'm an idiot & id changes stuff all the time :( 157 | nodeNick = nodeNick.substring(nodeNick.indexOf("") + "".length, nodeNick.indexOf("")); 158 | nodeNick = nodeNick.indexOf("
    ") >= 0 ? nodeNick.substring(0, nodeNick.indexOf("
    ")) : nodeNick; 159 | 160 | if (nodeNick != nick) 161 | return; 162 | 163 | // Idle 164 | if (!status) { 165 | // cut off status 166 | $(that).html(prehtml.substring(prehtml.indexOf("
    "))); 167 | 168 | var rosteritem = $(that).parent().parent(); 169 | 170 | $(rosteritem).children(".rosteritem-name").css({ 'margin-bottom': '0px' }); 171 | 172 | if ($(rosteritem).hasClass('rosteritem-selected')) 173 | $(rosteritem).css({ 'height': '26px' }); 174 | else 175 | $(rosteritem).css({ 'height': '20px' }); 176 | 177 | return; 178 | } 179 | 180 | 181 | // Online game 182 | if (status.ADDRESS.length > 8) { 183 | $.ajax({ 184 | url: "/browser/details", 185 | data: { 186 | ids: status.SERVER_ID 187 | }, 188 | dataType: "json", 189 | port: "joinserver", 190 | cache: false, 191 | success: function (x) { 192 | if (($.isArray(x) || x.length > 0) && x[0]) { 193 | var gametype = (typeof GameTypes[x[0].game_type] != 'undefined') ? GameTypes[x[0].game_type] : x[0].game_type_title; 194 | 195 | if (prehtml.indexOf(" 0) 196 | prehtml = prehtml.substring(0, prehtml.indexOf("
    ")); 197 | 198 | var str = prehtml + "

    "; 199 | 200 | // teamgame 201 | if (x[0].teamsize) { 202 | str += "" + gametype + " on "; 203 | 204 | if (x[0].g_gamestate === "PRE_GAME" && x[0].num_players < (x[0].teamsize * 2)) 205 | str += x[0].map_title + " - " + Locations[x[0].location_id] + " (" + x[0].num_players + "/" + (x[0].teamsize * 2) + ")

    "; 206 | else 207 | str += x[0].map_title + " - " + Locations[x[0].location_id] + " (" + x[0].num_players + "/" + (x[0].teamsize * 2) + ")

    "; 208 | } 209 | 210 | // duel 211 | else 212 | str += "" + gametype + " on " + x[0].map_title + " - " + Locations[x[0].location_id] + " (" + x[0].num_players + "/" + x[0].max_clients + ")

    "; 213 | 214 | $(that).html(str); 215 | 216 | $(that).parent().parent().css({ 'height': '35px' }); 217 | } 218 | } 219 | }); 220 | 221 | // Prac game 222 | } else if (status.ADDRESS === "loopback") { 223 | if (prehtml.indexOf(" 0) 224 | prehtml = prehtml.substring(0, prehtml.indexOf("
    ")); 225 | 226 | $(that).html(prehtml + "

    Playing Practice Game

    "); 227 | 228 | $(that).parent().parent().css({ 'height': '35px' }); 229 | 230 | 231 | // Demo 232 | } else if (status.ADDRESS === "bot") { 233 | if (prehtml.indexOf(" 0) 234 | prehtml = prehtml.substring(0, prehtml.indexOf("
    ")); 235 | 236 | $(that).html(prehtml + "

    Watching demo

    "); 237 | 238 | $(that).parent().parent().css({ 'height': '35px' }); 239 | } 240 | }); 241 | } 242 | catch (e) { } 243 | } 244 | 245 | function updateRoster() { 246 | try { 247 | if (unsafeWindow.fullScreen) 248 | return; 249 | 250 | var items = unsafeWindow.quakelive.xmppClient.roster.items; 251 | 252 | for (var i in items) { 253 | if (items[i].status && typeof items[i].status != "undefined") { 254 | updateNode(items[i].jid.username, items[i].status); 255 | } 256 | } 257 | } 258 | catch (e) { } 259 | } 260 | 261 | /* 262 | Iterates over roster.items from the xmppClient object and updates status where due 263 | */ 264 | 265 | function initRoster() { 266 | $ = unsafeWindow.jQuery; 267 | 268 | // Update roster, so status is the correct info regarding players/maxplayers etc 269 | window.setInterval(updateRoster, 120 * 1000); 270 | 271 | GM_addStyle(".rosteritem-selected { text-align:left;font-weight:bold;font-size:12px;height:26px;line-height:20px;border-bottom:1px solid #a1a0a3;background:url() repeat-y #fff !important }"); 272 | 273 | // resize chat area 274 | window.setTimeout(function () { 275 | updateRoster(); 276 | //$("#im-body").css({'height':'500px'}); 277 | GM_addStyle(".rosteritem-selected { text-align:left;font-size:12px;height:26px;line-height:20px;border-bottom:1px solid #a1a0a3;background:url() repeat-y #fff !important }"); 278 | GM_addStyle(".rosteritem-selected .rosteritem-name { font-weight:bold;line-height:20px; !important }"); 279 | 280 | }, 10 * 1000); 281 | } 282 | 283 | init(); 284 | })(window); 285 | -------------------------------------------------------------------------------- /source/scripts/attic/profileJumper.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 113249 3 | // @name Quake Live Better Profile Jumper 4 | // @version 1.4 5 | // @namespace phob.net 6 | // @author wn 7 | // @description Keeps your current Profile tab when using "Jump to profile..." 8 | // @run-at document-end 9 | // ==/UserScript== 10 | 11 | 12 | if (/offline/i.test(document.title) || window.self != window.top) { 13 | return; 14 | } 15 | 16 | quakelive.mod_profile.ProfileJumpClick = function() { 17 | var player_name = $("#profile_jump_input").val(); 18 | if ("undefined" == typeof player_name) return; 19 | 20 | player_name = $.trim(player_name); 21 | if (!player_name.length) return; 22 | 23 | var parts = [quakelive.pathParts[0], quakelive.pathParts[1], player_name]; 24 | if (/^\d{4}(?:-\d{2}){2}$/.test(quakelive.pathParts[3])) parts.push(quakelive.pathParts[3]); 25 | 26 | quakelive.Goto(parts.join("/")); 27 | return false; 28 | } 29 | -------------------------------------------------------------------------------- /source/scripts/attic/raceTop10.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 424643 3 | // @name Quake Live Race Leaders 4 | // @description Adds a "top10" command to the QL console to show the top 10 race scores 5 | // @version 1.2 6 | // @author PredatH0r 7 | // @include http://*.quakelive.com/* 8 | // @exclude http://*.quakelive.com/forum* 9 | // @unwrap 10 | // ==/UserScript== 11 | 12 | /* 13 | 14 | Use "top10 ." to get the top scores for the current map. 15 | Use "top10 basesiege" to get the top scores for map "basesiege". 16 | You can bind this script to a key (e.g. "P") by using: /bind p "top10 ." 17 | 18 | The output mode can be configured by setting _race_outputMethod to either echo/print/say_team/say 19 | If no _race_outputMethod is set, the _qlrd_outputMethod is used, which can be toggled by the QLrank.com Display script. 20 | If neither is set, "echo" will be used. 21 | 22 | Version 1.2 23 | - fixed LaunchGame, which caused incompatibilities with other scripts (e.g. ingame-friend-commands) 24 | 25 | Version 1.1 26 | - removed clan tags from names 27 | 28 | Version 1.0 29 | - first version working with QLHM 30 | 31 | */ 32 | 33 | (function () { 34 | // external global variables 35 | var quakelive = window.quakelive; 36 | var qz_instance = window.qz_instance; 37 | var console = window.console; 38 | 39 | // constants 40 | var COMMAND = "top10"; 41 | var INFO = '"Use \'^5' + COMMAND + ' .^7\' or \'^5' + COMMAND + ' ^7<^5mapname^7>\' to show top 10 race scores"'; 42 | var CVAR_RACE_OUTPUTMETHOD = "_race_outputMethod"; 43 | var CVAR_QLRD_OUTPUTMETHOD = "_qlrd_outputMethod"; 44 | 45 | // local variables 46 | var oldOnCvarChanged; 47 | var oldLaunchGame; 48 | var activeServerReq = false; 49 | 50 | function log(msg) { 51 | if (quakelive.IsGameRunning()) 52 | qz_instance.SendGameCommand("echo \"" + msg + "\""); 53 | else 54 | console.log(msg); 55 | }; 56 | 57 | function init() { 58 | if (!oldOnCvarChanged) { 59 | oldOnCvarChanged = window.OnCvarChanged; 60 | oldLaunchGame = window.LaunchGame; 61 | } 62 | window.LaunchGame = launchGame; 63 | window.OnCvarChanged = onCvarChanged; 64 | } 65 | 66 | function launchGame(params, server) { 67 | params.Append('+set ' + COMMAND + ' "^7"'); 68 | params.Append('+set ' + COMMAND + ' ' + INFO); 69 | return oldLaunchGame.call(this, params, server); 70 | } 71 | 72 | function onCvarChanged(name, value, rsync) { 73 | try { 74 | if (name == COMMAND) { 75 | if (value && !activeServerReq) { 76 | activeServerReq = true; 77 | getData(value); 78 | } 79 | quakelive.cvars.Set(COMMAND, INFO); 80 | return; 81 | } 82 | } catch (e) { 83 | log("onCvarChanged:" + e); 84 | activeServerReq = false; 85 | return; 86 | } 87 | oldOnCvarChanged(name, value, rsync); 88 | } 89 | 90 | function getData(mapName) { 91 | try { 92 | if (mapName != "" && mapName != ".") { 93 | requestMapData(mapName); 94 | return; 95 | } 96 | 97 | quakelive.serverManager.RefreshServerDetails(quakelive.currentServerId, { 98 | cacheTime: 0, 99 | onSuccess: onServerDetailsRefreshed, 100 | onError: function() { 101 | log("^1Failed to update server info^7"); 102 | activeServerReq = false; 103 | } 104 | }); 105 | } catch (e) { 106 | log("getData: " + e); 107 | activeServerReq = false; 108 | } 109 | } 110 | 111 | function onServerDetailsRefreshed() { 112 | try { 113 | var server = quakelive.serverManager.GetServerInfo(quakelive.currentServerId); 114 | var map = server.map; 115 | requestMapData(map); 116 | } catch (e) { 117 | log("onServerDetailsRefreshed: " + e); 118 | activeServerReq = false; 119 | } 120 | } 121 | 122 | function requestMapData(map) { 123 | $.getJSON("/race/map/" + map, function (data) { processData(data, map); }); 124 | } 125 | 126 | function processData(data, map) { 127 | try { 128 | var method = quakelive.cvars.Get(CVAR_RACE_OUTPUTMETHOD).value; 129 | if (!method) 130 | method = quakelive.cvars.Get(CVAR_QLRD_OUTPUTMETHOD).value; 131 | if (!method) 132 | method = "echo"; 133 | 134 | var i; 135 | var count = data.scores.length; 136 | if (count > 10) 137 | count = 10; 138 | var renderState = { hasPersonal: false, output: [ ], lineLength: 0 }; 139 | 140 | for (i = 0; i < count; i++) { 141 | var score = data.scores[i]; 142 | renderScore(method, i+1, score, renderState); 143 | } 144 | 145 | if (data.personal && !renderState.hasPersonal) { 146 | renderScore(method, data.personal.rank + 1, data.personal, renderState); 147 | } 148 | 149 | var delay = method == "say" || method == "say_team" ? 1050 : 0; 150 | renderState.output.splice(0, 0, "^7Top 10 race results on ^3" + map + "^7"); 151 | printLines(method, delay, renderState.output, 0); 152 | } catch (e) { 153 | log("processData:" + e); 154 | activeServerReq = false; 155 | } 156 | } 157 | 158 | function renderScore(method, rank, score, renderState) { 159 | try { 160 | var isPersonal = false; 161 | var color = "^2"; 162 | if (score.name == quakelive.username) { 163 | color = "^1"; 164 | isPersonal = true; 165 | } 166 | 167 | //var clan = score.PLAYER_CLAN; 168 | var name = score.name; 169 | 170 | var seconds = Math.floor(score.score / 1000); 171 | var millis = "00" + (score.score % 1000); 172 | millis = millis.substr(millis.length - 3, 3); 173 | var time = seconds + "." + millis; 174 | 175 | rank = rank < 10 ? "0" + rank : "" + rank; 176 | 177 | if (method == "echo") { 178 | var when = window.formatDate(new Date(score.date), 'yyyy-MM-dd HH:mm'); 179 | renderState.output.push(color + rank + "^7 " + when + " " + color + time + "^7 " + name); 180 | } 181 | else { 182 | var msg = " " + color + rank + "^7 " + name + " ^5" + time; 183 | if (renderState.output.length > 0 && renderState.output[renderState.output.length - 1].length + msg.length < 90) { 184 | renderState.output[renderState.output.length - 1] += msg; 185 | } else { 186 | renderState.output.push(msg); 187 | } 188 | } 189 | renderState.hasPersonal |= isPersonal; 190 | } catch (e) { 191 | log("renderScore:" + e); 192 | } 193 | } 194 | 195 | function printLines(method, delay, lines, index) { 196 | try { 197 | if (index >= lines.length) { 198 | activeServerReq = false; 199 | return; 200 | } 201 | 202 | qz_instance.SendGameCommand(method + " " + lines[index]); 203 | if (delay <= 0) 204 | printLines(method, delay, lines, index + 1); 205 | else 206 | window.setTimeout(function() { printLines(method, delay, lines, index + 1); }, delay); 207 | } catch (e) { 208 | log("printLines: " + e); 209 | } 210 | } 211 | 212 | init(); 213 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/raceboard.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 465415 3 | // @name Quake Live QLStats Race Leader Boards 4 | // @version 1.1 5 | // @author PredatH0r 6 | // @contributors Rulex 7 | // @description Show separate leader boards for PQL/VQL with/without weapons and option to list all maps for a specific user 8 | // @include http://*.quakelive.com/* 9 | // @exclude http://*.quakelive.com/forum* 10 | // @unwrap 11 | // ==/UserScript== 12 | 13 | /* 14 | 15 | This script modifies the "Community / Race Leaders" page inside the Quake Live UI and adds links to alternative leader boards for 16 | - PQL with weapons 17 | - PQL strafe only 18 | - VQL with weapons 19 | - VQL strafe only 20 | and provides an overview of a player's top-scores on all maps. 21 | 22 | Data is taken from Rulex' QLStats database hosted on http://ql.leeto.fi/ 23 | Big thanks to Rulex for providing the data and making the necessary API changes to enable the race leader boards. 24 | 25 | Version 1.1 26 | - added leader and top score to "All maps" view 27 | 28 | Version 1.0 29 | - first public release 30 | 31 | */ 32 | 33 | (function () { 34 | var API_URL = "http://ql.leeto.fi/"; 35 | //var API_URL = "http://127.0.0.1:8585/"; 36 | 37 | var oldShowBoard; 38 | var mode = -2; 39 | var updateBusy = false; 40 | 41 | function init() { 42 | addStyle( 43 | "#raceBoardLinks { color: white; padding: 3px 10px; font-size: 14px; }", 44 | "#raceBoardLinks a { color: white; font-size: 14px; text-decoration: underline; }", 45 | "#raceBoardLinks a.active { color: #FF4422; }", 46 | "#raceBoardLinks input { margin-left: 15px; margin-right: 4px; margin-top: 1px; }", 47 | "#raceBoardUpdate { float: right; margin-right: 10px; }", 48 | "#raceBoardUpdate input[type=text] { height: 22px; width: 150px; padding: 0 4px; }", 49 | "#raceBoardUpdate input[type=button] { height: 22px; width: 100px; margin-left: 5px; cursor: pointer; }", 50 | ".raceBoardYourScore { font-size: 18pt; font-weight: normal; text-align: right; padding-right: 10px; margin-top: 5px; }", 51 | ".raceBoardYourScoreSmall { font-size: 14pt; font-weight: normal; text-align: right; padding-right: 10px; }", 52 | ".raceBoardLeaderScore { font-size: 10pt; font-weight: normal; text-align: right; padding-right: 10px; }" 53 | ); 54 | 55 | oldShowBoard = quakelive.mod_race.ShowBoard; 56 | quakelive.mod_race.ShowBoard = showOfficialBoard; 57 | quakelive.AddHook("OnContentLoaded", onContentLoaded); 58 | onContentLoaded(); 59 | } 60 | 61 | function addStyle(/*...*/) { 62 | var css = ""; 63 | for (var i = 0; i < arguments.length; i++) 64 | css += "\n" + arguments[i]; 65 | $("head").append(""); 66 | } 67 | 68 | function onContentLoaded() { 69 | var $racedesc = $("#mod_race #header .racedesc"); 70 | $racedesc.css("padding-bottom", "7px"); 71 | $racedesc.after(""); 79 | $("#mapchooser").parent().prepend( 80 | "
    Force update of QLStats database:" + 81 | "
    "); 82 | $("#raceBoardLinks a").click(function () { 83 | mode = $(this).data("mode"); 84 | updateHighlight(); 85 | if (mode == -1) 86 | $("#raceBoardJustMe").attr("checked", false); 87 | showQlstatsBoard(mode); 88 | }); 89 | $("#raceBoardLinks input").click(showQlstatsBoard); 90 | $("#raceBoardUpdate input:button").click(updateQlstats); 91 | $("#mod_race .scores").data("fill-height", "350"); // enable auto-sizing through extraQL 92 | } 93 | 94 | function updateHighlight() { 95 | $("#raceBoardLinks a.active").removeClass("active"); 96 | if (mode >= -1) 97 | $("#raceBoardLinks a:eq(" + (mode + 1) + ")").addClass("active"); 98 | } 99 | 100 | function updateQlstats() { 101 | if (updateBusy) 102 | return; 103 | var player = $("#raceBoardUpdateNickname").val(); 104 | if (!player) 105 | return; 106 | updateBusy = true; 107 | $("#raceBoardUpdateButton").val("Updating...").attr("disabled", true); 108 | $.ajax({ 109 | url: API_URL + "api/players/" + encodeURIComponent(player) + "/update", 110 | dataType: "jsonp", 111 | success: function () { 112 | $("#raceBoardUpdateButton").val("Update player").attr("disabled", false); 113 | showQlstatsBoard(); 114 | }, 115 | error: function() { 116 | $("#raceBoardUpdateButton").val("Update failed").attr("disabled", false); 117 | }, 118 | complete: function() { 119 | updateBusy = false; 120 | } 121 | }); 122 | } 123 | 124 | function showOfficialBoard(map) { 125 | oldShowBoard.call(quakelive.mod_race, map); 126 | mode = -1; 127 | updateHighlight(); 128 | $("#raceBoardJustMe").attr("checked", false); 129 | } 130 | 131 | function showQlstatsBoard() { 132 | var justMe = $("#raceBoardJustMe").attr("checked"); 133 | if (justMe && mode < 0) { 134 | mode = 0; 135 | updateHighlight(); 136 | } 137 | if (mode == -1) { 138 | $("#mapchooser").trigger("change"); 139 | return; 140 | } 141 | var map = $("#mapchooser").val(); 142 | var url = API_URL; 143 | url += justMe ? "api/race/players/" + quakelive.username : "api/race/maps/" + map; 144 | url += "?ruleset=" + ((mode & 2) ? "vql" : "pql"); 145 | url += "&weapons=" + ((mode & 1) ? "off" : "on"); 146 | url += "&limit=100"; 147 | url += "&player=" + quakelive.username; 148 | $.ajax({ 149 | url: url, 150 | dataType: "jsonp", 151 | success: function(data) { fillBoard(data, justMe); }, 152 | error: function() { fillBoard(); } 153 | }); 154 | } 155 | 156 | function fillBoard(data, justMe) { 157 | var $scores = $("#mod_race .scores"); 158 | $scores.empty(); 159 | 160 | if (!data || !data.data || !data.data.scores) { 161 | $scores.text("Could not load data"); 162 | return; 163 | } 164 | 165 | var playerIndex = -1; 166 | var html = ""; 167 | $.each(data.data.scores, function (i, pb) { 168 | html += renderScore(pb, false, justMe); 169 | if (pb.PLAYER_NICK == quakelive.username) 170 | playerIndex = i; 171 | }); 172 | $scores.append(html); 173 | 174 | html = ""; 175 | var $pb = $("#mod_race .personalbest"); 176 | $pb.empty(); 177 | if (playerIndex >= 0 && !justMe) 178 | html += renderScore(data.data.scores[playerIndex], true, justMe); 179 | $pb.append(html); 180 | } 181 | 182 | function renderScore(pb, personal, justMe) { 183 | var medal = personal ? " personalscore" : pb.RANK == 1 ? " gold" : pb.RANK == 2 ? " silver" : pb.RANK == 3 ? " bronze" : ""; 184 | var html = ""; 185 | var descr; 186 | var leaderInfo = ""; 187 | var scoreStyle = "raceBoardYourScore"; 188 | if (justMe) { 189 | html += ""; 190 | descr = pb.MAP; 191 | if (pb.LEADER_SCORE) 192 | leaderInfo = "
    " + pb.LEADER_NICK + ": " + formatScore(pb.LEADER_SCORE) + "
    "; 193 | scoreStyle = "raceBoardYourScoreSmall"; 194 | } else { 195 | html += "
    "; 196 | descr = pb.PLAYER_NICK; 197 | } 198 | html += "
    "; 199 | html += "
    "; 200 | html += "
    " + pb.RANK + ". " + descr + " "; 201 | html += "
    " + window.formatDate(new Date(pb.GAME_TIMESTAMP), 'yyyy-MM-dd HH:mm') + "
    "; 202 | html += "
    "; 203 | html += "
    "; 204 | html += "
    " + formatScore(pb.SCORE) + "
    "; 205 | html += leaderInfo; 206 | html += "
    "; 207 | html += "
    "; 208 | html += "
    "; 209 | return html; 210 | } 211 | 212 | function formatScore(score) { 213 | if (!score) return ""; 214 | if (!score.substring) score = score.toString(); 215 | return score.substring(0, score.length - 3) + "." + score.slice(-3); 216 | } 217 | 218 | init(); 219 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/resizeLayout.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id resizeLayout 3 | // @name Quake Live Layout Resizer 4 | // @version 1.2 5 | // @author PredatH0r 6 | // @description Optimize the QL web UI to take advantage of your actual screen/window size 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /* 11 | 12 | This script finds the optimal layout for the QL chat popup, based on the current window size. 13 | 14 | When the window is less then 1310 pixels wide, the chat will overlap the content area, 15 | but leaves some space on the top (configurable by web_chatOverlapIndent, default 150) 16 | so you can access the navigation menus and "Customize" in the server browser. 17 | 18 | If the window is wider, the chat will be shown full-height outside the content area. 19 | 20 | Version 1.2 21 | - removed re-appearing "send" button in chat window (again) 22 | 23 | Version 1.1 24 | - removed re-appearing "send" button in chat window 25 | 26 | Version 1.0 27 | - twitter iframe is now also resized to use full screen height 28 | - fixed in-game chat/friend list 29 | 30 | Version 0.9 31 | - chat is now correctly resizing 32 | - server browser detail window is now correctly resizing 33 | - centered pages are pushed to the left if they would be hidden by the chat window 34 | 35 | Version 0.8 36 | - restored most of the original functions that got lost due to cross-domain CSS loading 37 | 38 | Version 0.7 39 | - hotfix to prevent endless-loop when loading QL 40 | 41 | Version 0.6 42 | - updated extraQL script url to sourceforge 43 | 44 | Version 0.5 45 | - fixed z-index to work with standard content, chat window, drop-down menus and dialog boxes 46 | 47 | Version 0.4 48 | - switched back to horizontal "Chat" bar, always located on the bottom edge 49 | - introduced cvar web_chatOverlapIndent to customize how much space should be left on 50 | top, when the chat is overlapping the main content area 51 | - adjusts z-index of chat and navigation menu to prevent undesired overlapping 52 | 53 | 54 | CVARS: 55 | - web_chatOverlapIndent: number of pixels to skip from top-edge that won't be overlapped 56 | by an expanded chat window 57 | 58 | */ 59 | 60 | (function () { 61 | // external variables 62 | var quakelive = window.quakelive; 63 | var extraQL = window.extraQL; 64 | 65 | // constants 66 | var RIGHT_MARGIN = 0; 67 | var CVAR_chatOverlapIndent = "web_chatOverlapIndent"; 68 | 69 | // variables 70 | var oldOnResize; 71 | var oldOnCvarChanged; 72 | var oldSelectContact; 73 | var oldRenderMatchDetails; 74 | var styleFullHeight; 75 | 76 | 77 | function init() { 78 | extraQL.addStyle(".chatBox { " + 79 | "border-left: 3px solid #444; " + 80 | "border-top: 3px solid #444; " + 81 | "border-right: 3px solid #444; " + 82 | "position: fixed; " + 83 | "bottom: 27px; " + 84 | "right: 0px; " + 85 | "}"); 86 | extraQL.addStyle( 87 | "#chatContainer.expanded #collapsableChat { background-color: rgb(114,24,8); }", 88 | "#chatContainer .fullHeight { height: 550px; }", 89 | "#im-chat #im-chat-send { left: 300px; }" // "display" gets overruled, so use "left" to move it off-screen 90 | ); 91 | 92 | // z-index adjustments 93 | $("#qlv_content").css("z-index", "auto"); // will be toggled between 1/auto to bring the chat in front/behind the drop down menus 94 | $("#newnav_top").css("z-index", "2103"); 95 | $("ul.sf-menu *").css("z-index", "2102"); 96 | $("#lgi_cli").css("z-index", "10003"); // has 1003 and would be overlapped by menu bar items in #newnav_top 97 | 98 | // chat 99 | $("#chatContainer").width(3 + 300 + 3).css("right", RIGHT_MARGIN + "px"); 100 | $("#collapsableChat").addClass("bottomDockBar"); 101 | $("#qlv_chatControl").css("height", "auto"); 102 | $("#qlv_chatControl").addClass("chatBox"); 103 | 104 | if (quakelive.cvars.Get(CVAR_chatOverlapIndent).value == "") 105 | quakelive.cvars.Set(CVAR_chatOverlapIndent, 140); 106 | 107 | oldOnResize = window.onresize; 108 | window.onresize = onResize; 109 | oldOnCvarChanged = window.OnCvarChanged; 110 | window.OnCvarChanged = onCvarChanged; 111 | quakelive.mod_friends.FitToParent = updateChatAndContentLayout; 112 | 113 | oldSelectContact = quakelive.mod_friends.roster.SelectContact.bind(quakelive.mod_friends.roster); 114 | quakelive.mod_friends.roster.SelectContact = function(contact) { 115 | oldSelectContact(contact); 116 | updateChatAndContentLayout(); 117 | }; 118 | 119 | oldRenderMatchDetails = quakelive.matchcolumn.RenderMatchDetails; 120 | quakelive.matchcolumn.RenderMatchDetails = function () { 121 | oldRenderMatchDetails.apply(quakelive.matchcolumn, arguments); 122 | resizeBrowserDetails(); 123 | } 124 | 125 | findCssRules(); 126 | updateChatAndContentLayout(); 127 | } 128 | 129 | function findCssRules() { 130 | var i, j; 131 | for (i = 0; i < document.styleSheets.length; i++) { 132 | var sheet = document.styleSheets[i]; 133 | if (!sheet.cssRules) continue; 134 | for (j = 0; j < sheet.cssRules.length; j++) { 135 | try { 136 | var rule = sheet.rules[j]; 137 | if (rule.cssText.indexOf("#chatContainer .fullHeight") == 0) 138 | styleFullHeight = rule.style; 139 | } 140 | catch (e) {} 141 | } 142 | } 143 | } 144 | 145 | function onResize(event) { 146 | if (oldOnResize) 147 | oldOnResize(event); 148 | 149 | try { updateChatAndContentLayout(); } 150 | catch (ex) { } 151 | } 152 | 153 | function onCvarChanged(name, val, replicate) { 154 | oldOnCvarChanged(name, val, replicate); 155 | try { 156 | if (name == CVAR_chatOverlapIndent) 157 | updateChatAndContentLayout(); 158 | } 159 | catch (e) { } 160 | } 161 | 162 | function updateChatAndContentLayout() { 163 | try { 164 | var $window = $(window); 165 | var width = $window.width(); 166 | 167 | // reposition background image and content area 168 | var margin; 169 | var minExpandedWidth = 3 + 1000 + 7 + 3 + 300 + 3 + RIGHT_MARGIN; 170 | if (width <= minExpandedWidth) { 171 | $("body").css("background-position", "-518px 0"); 172 | margin = "0 3px 0 3px"; 173 | } else if (width <= minExpandedWidth + 7 + 3 + 300 + 3) { 174 | $("body").css("background-position", (-518 + width - minExpandedWidth).toString() + "px 0"); 175 | margin = "0 3px 0 " + (width - 1313).toString() + "px"; 176 | } else { 177 | $("body").css("background-position", "center top"); 178 | margin = "0 auto"; 179 | } 180 | $("#qlv_container").css("margin", margin); 181 | 182 | // push profile page and others to the left so they are not hidden by the chat 183 | $("#qlv_contentBody.twocol_left").css("left", Math.min(155, (155 - (minExpandedWidth - 150 - 7 - width))) + "px"); 184 | 185 | // modify height of elements that support it 186 | var height = $window.height(); 187 | $("div:data(fill-height)").each(function() { 188 | var $this = $(this); 189 | $this.height(height - parseInt($this.data("fill-height"))); 190 | }); 191 | 192 | // modify height of chat 193 | if (quakelive.IsGameRunning()) { 194 | $("#collapsableChat").css("display", "none"); // hide in-game chat bar 195 | $("#qlv_chatControl").css("display","none"); 196 | height = Math.floor(height) * 0.9 - 35; // 10% clock, 35px buttons 197 | } else { 198 | $("#collapsableChat").css("display", ""); 199 | $("#qlv_chatControl").css("display", ""); 200 | } 201 | height -= 3 + 27 + 14; // height of top border + title bar + bottom bar 202 | 203 | var topOffset = 140; // leave header and menu visible when chat is overlapping the content area 204 | if (width < minExpandedWidth - 7) { 205 | try { 206 | topOffset = parseInt(quakelive.cvars.Get(CVAR_chatOverlapIndent).value); 207 | } catch (e) { 208 | } 209 | height -= topOffset; 210 | 211 | } else { 212 | height -= 7; // leave some gap from top edge 213 | } 214 | 215 | // create more space for "Active Chat" 216 | var footerHeight = 400; // 210 by default 217 | if (height - footerHeight < 300) 218 | footerHeight = height - 300; 219 | $("#im").css("height", "auto"); 220 | $("#im-body").height(height - footerHeight); 221 | $("#im-footer").css({ "background": "#222", "padding": "0 5px", height: footerHeight + "px" }); 222 | $("#im-chat").css({ "background-clip": "content-box", "height": (footerHeight - 8) + "px" }); 223 | $("#im-chat-body").css({ left: 0, top: "13px", width: "284px", "background-color": "white", height: (footerHeight - 8 - 13 - 6 - 33 - 6) + "px" }); 224 | $("#im-chat input").css({ width: "282px", left: 0, top: "auto", bottom: "7px" }); 225 | $("#im-overlay-body").css({ "background-color": "white", height: (height - 87) + "px" }); 226 | 227 | // resize elements which support a dynamic height 228 | if (styleFullHeight) 229 | styleFullHeight.setProperty("height", height + "px"); 230 | $("#chatContainer [data-fill]").each(function() { 231 | var $this = $(this); 232 | $this.height(height - parseInt($this.data("fill"))); 233 | }); 234 | 235 | 236 | // modify z-index to deal with drop-down-menus 237 | $("#qlv_content").css("z-index", topOffset >= 110 ? "auto" : "1"); // #chatContainer has z-index 999 238 | $("#newnav_top").css("z-index", "2103"); 239 | $("ul.sf-menu *").css("z-index", "2102"); 240 | $("#lgi_cli").css("z-index", "10003"); 241 | 242 | resizeBrowserDetails(); 243 | 244 | } catch (ex) { 245 | extraQL.log(ex); 246 | } 247 | } 248 | 249 | function resizeBrowserDetails() { 250 | $("#browser_details ul.players.miniscroll").css("max-height", ($(window).height() - 496) + "px"); 251 | } 252 | 253 | init(); 254 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/rosterGroup.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id qlrostergroup 3 | // @name Roster Group 4 | // @version 1.2 5 | // @namespace Lam 6 | // @author Lam 7 | // @description 8 | // @include http://www.quakelive.com/* 9 | // @exclude http://www.quakelive.com/forum/* 10 | // ==/UserScript== 11 | 12 | 13 | function contentEval(source) { 14 | if ('function' == typeof source) { 15 | source = '(' + source + ')();'; 16 | } 17 | var script = document.createElement('script'); 18 | script.setAttribute('type', 'application/javascript'); 19 | script.textContent = source; 20 | document.body.appendChild(script); 21 | document.body.removeChild(script); 22 | } 23 | 24 | 25 | contentEval(function () { 26 | if (typeof quakelive != 'object') { return; } 27 | 28 | // TODO: make this actually configurable :) 29 | var groupname1 = "huhu"; 30 | var groupmembers1str = "Barracouda Bananas NetMaori monja Fillekiller baanze jala1 bakwardsman jaszman macen nathanfadezz niewi rklacks scaretaker somnabulant"; 31 | var groupmembers1 = groupmembers1str.toLowerCase().replace(/ /g,',').replace(/,,/g,',').split(','); 32 | var groupname2 = ""; 33 | var groupmembers2str = ""; 34 | var groupmembers2 = groupmembers2str.toLowerCase().replace(/ /g,',').replace(/,,/g,',').split(','); 35 | var groupname3 = "FFA"; 36 | var groupmembers3str = "sya_ oswrap bogotac yakumo crowax gandim nrn437 ghenghis"; 37 | var groupmembers3 = groupmembers3str.toLowerCase().replace(/ /g,',').replace(/,,/g,',').split(','); 38 | 39 | // var oldOnRosterUpdated = quakelive.mod_friends.roster.UI_OnRosterUpdated; 40 | // quakelive.mod_friends.roster.UI_OnRosterUpdated = function () { 41 | // 42 | // } 43 | 44 | // this is used by UI_ShowApp to create the roster window 45 | quakelive.mod_friends.TPL_FRIENDS_LIST = quakelive.mod_friends.TPL_FRIENDS_LIST.replace('' 46 | + '

    ' + groupname1 + '

    ' 47 | + '

    ' + groupname2 + '

    ' 48 | + '

    ' + groupname3 + '

    ' 49 | + '= 0 ) 53 | return 'group1'; 54 | else if ( groupmembers2.indexOf( nick.toLowerCase() ) >= 0 ) 55 | return 'group2'; 56 | else if ( groupmembers3.indexOf( nick.toLowerCase() ) >= 0 ) 57 | return 'group3'; 58 | return 'online'; 59 | } 60 | 61 | var oldImportRosterData = quakelive.mod_friends.roster.ImportRosterData; 62 | quakelive.mod_friends.roster.ImportRosterData = function() { 63 | oldImportRosterData.apply(this, arguments); 64 | //window.console.log("ImportRosterData fired"); 65 | for (var i = 0; i < quakelive.mod_friends.roster.fullRoster.length; i++ ) { 66 | var putinto = QLRG_ContactGroup( quakelive.mod_friends.roster.fullRoster[ i ].name ); 67 | if ( quakelive.mod_friends.roster.fullRoster[ i ].CanDisplayOnRoster() 68 | && ( putinto != 'online' ) ) { 69 | //window.console.log("ImportRosterData: " + quakelive.mod_friends.roster.fullRoster[ i ].name + ', group: ' + quakelive.mod_friends.roster.fullRoster[ i ].group + '.' ); 70 | quakelive.mod_friends.roster.fullRoster[ i ].UI_PlaceInGroup(putinto, true); 71 | /* 72 | * contact.StartInactivityTimeout() has hardcoded group to which the contact 73 | * returns after 3 minutes (length also hardcoded). Best thing to do would be 74 | * to override contact.UI_PlaceInGroup to automatically change 'online' to 75 | * my group, but I'm too noob to do that. 76 | */ 77 | quakelive.mod_friends.roster.fullRoster[ i ].StartInactivityTimeout = function(){ 78 | if (this.timeoutHandle) { 79 | clearTimeout(this.timeoutHandle) 80 | } 81 | var contact = this; 82 | this.timeoutHandle = setTimeout(function() { 83 | if (quakelive.mod_friends.roster.selectedContact != contact) { 84 | if (contact.group == 'active') { 85 | if (contact.CanDisplayOnRoster()) { 86 | contact.UI_PlaceInGroup(QLRG_ContactGroup(contact.name)) 87 | } else { 88 | contact.UI_RemoveFromGroup() 89 | } 90 | contact.timeoutHandle = null 91 | } 92 | } else { 93 | contact.StartInactivityTimeout() 94 | } 95 | }, 180 * 1000) 96 | }; 97 | } 98 | } 99 | } 100 | 101 | var OldPresence = IM_OnPresence; 102 | IM_OnPresence = function(presence_json) { 103 | OldPresence.apply(this, arguments); 104 | var pres = quakelive.Eval(presence_json); 105 | if (pres) { 106 | var jid = new JID(pres.who); 107 | //window.console.log("Test 2: " + jid.username); 108 | if ( QLRG_ContactGroup( jid.username ) != 'online' ) { 109 | var contact = quakelive.mod_friends.roster.GetContactByJID(jid); 110 | if (contact) { 111 | //window.console.log('Test 3'); 112 | if (contact.CanDisplayOnRoster()) { 113 | //window.console.log('Prev group: ' + contact.group + '.'); 114 | contact.UI_PlaceInGroup(QLRG_ContactGroup(contact.name)); 115 | // same as above 116 | contact.StartInactivityTimeout = function(){ 117 | if (this.timeoutHandle) { 118 | clearTimeout(this.timeoutHandle) 119 | } 120 | var contact = this; 121 | this.timeoutHandle = setTimeout(function() { 122 | if (quakelive.mod_friends.roster.selectedContact != contact) { 123 | if (contact.group == 'active') { 124 | if (contact.CanDisplayOnRoster()) { 125 | contact.UI_PlaceInGroup(QLRG_ContactGroup(contact.name)) 126 | } else { 127 | contact.UI_RemoveFromGroup() 128 | } 129 | contact.timeoutHandle = null 130 | } 131 | } else { 132 | contact.StartInactivityTimeout() 133 | } 134 | }, 180 * 1000) 135 | }; 136 | } 137 | } 138 | } 139 | } 140 | } 141 | }); 142 | 143 | 144 | extraQL.addStyle( "#im-group1, #im-group2, #im-group3 { display: none }'" ); 145 | 146 | extraQL.addStyle( '#im-group1 .itemlist, #im-group2 .itemlist, #im-group3 .itemlist {\ 147 | color: #000000; display: block; font-family: Arial; font-size: 12px;\ 148 | font-style: normal; list-style: none outside none; margin: 0;\ 149 | padding: 0; text-align: center; top: 0; vertical-align: text-top; }' ); 150 | 151 | extraQL.addStyle( '#im-group1 h1, #im-group2 h1, #im-group3 h1 { display: block;\ 152 | height: 21px; margin: 0; padding: 0;\ 153 | text-align: left; width: 300px; padding-left: 30px; color:black;\ 154 | text-shadow: 0.1em 0.1em 0.2em white; font-family: Arial; font-style: italic;\ 155 | font-size:12px;\ 156 | background: url("data:image/jpeg;base64,\ 157 | /9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAIBAQEBAQIBAQECAgICAgQDAgICAgUEBAMEBgUGBgYF\ 158 | BgYGBwkIBgcJBwYGCAsICQoKCgoKBggLDAsKDAkKCgr/2wBDAQICAgICAgUDAwUKBwYHCgoKCgoK\ 159 | CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgr/wAARCAAVASwDAREA\ 160 | AhEBAxEB/8QAHAAAAgMAAwEAAAAAAAAAAAAAAwQAAQIFBwgJ/8QAaBAAAAIHAQMLGBQOAwAAAAAA\ 161 | AQIAAwQFBgcIEQkXIRIxMkFkcYWV09TVExQVGBkiQlZXWGFlgZOWoqaxxdHS4eTlIyU0NTY4UVRV\ 162 | ZnJ1goOUo6XE4uMmJygzR0hSY3N0hKHBw4akwv/EABgBAQEBAQEAAAAAAAAAAAAAAAABAgME/8QA\ 163 | HhEBAAMBAQACAwAAAAAAAAAAAAECEhETITFBUVL/2gAMAwEAAhEDEQA/AOwqYapK0X1LZmfkV1Kz\ 164 | JaCkZRWtDW8X83nxBQC0TGOd2ZQY4iKemK0HZjDVxPFvaVbvYarYjXr1y0FalSqihcY5jCNgFAAd\ 165 | 4CIiKazX+RzZp3VUAUdqTojEQ9Q7xaBD+7ICZ5UYGeFUWXOSK/l67WyTNBCTvqhEbRnLFfNb12oA\ 166 | lzQ/A6qd9ToY84om+E3rdTBLmv6DCqelThLPxvRAPum04/8AhLFacDKuf9TiuwTTVfRs9rP3KXFA\ 167 | wpqKqVIG/TIewhyWk6Sa1DKqpKowtgHj16j/AFh0mKgxalqhih5HHj3Ka3HK3LA6woxUbLU5UUXJ\ 168 | TBffI8tFqPOgIWqCoMclHz9HRJajzoDFqgn5wUeP3TJb20edBstUM8gycfv7TJb20edAYtUU6MSG\ 169 | Kj1+W++S3to86DCyqGd1o4mP37nbklvdI86AR6oZ75UfvzmPJb3SPOgEeqCfwYSx8/dM1vdI86AR\ 170 | qnqhhwFmA/Q0UW9tHnQDGpuo7gZiPsNFFvbR50GDVL1LjZiZkPsNElvbR50ATVJVODkZoP0NEFqX\ 171 | lAI1RlUJv0sP4OSVuW9yKXNAI1QdU58F+CIgzm5b3AozQDWT9qoHHnRE4e5eC3U0YoBGnrVQONO6\ 172 | LcH7LyXaijFAM08KqjY07owxsp6L/wDDOKTNAI07KruBnfG3MejV/hkFHKDAzoqy4t0efBeTXrAU\ 173 | coQzfpq3ttv2x/h5YNmxgpjNBL9NW/Fsj/TBs2LS5oJfpq34tkf6YNmxaM0FGnRVxbgnZMDTFt2K\ 174 | Rmgl+irji1zB0xbdikZoJfoq44tcwdMW3YpGaBV9Tuq9UOteuVTumCUxSWgIPJtCzk+dSMUHieoS\ 175 | te6Bw/M5sdjsq0nAzKSEKJFLLFb0VFLhHgSu0At5NiTNB6VoWkOyxDJtgZ2uCSrFC9jxC0h4fEQO\ 176 | UQsMA+UwY4CmYkdlS/pIi1XHi93RRCbed2uAwGYmtqda8yt5mMUBUnADsBwWCqKI4u1WrsXFIJcA\ 177 | JrY7LPIvaSkTg4wLZysAMv8Aki9dJ2AG8rypD5CGtkdgHZpJWj51gH9IAfVwR2BybJJABw7jg+T2\ 178 | f6gRqIDquRgGALGLD/C+wmtwDEkXh8xgHxf2UbgGJIkRDCyBzvvI3A2EircZl6RG4EGROJw7aW/A\ 179 | tRvv0KvFm9a/N95G4EvF5jDnfeRuBd4wfWgc77yNwJeI/cfN95G4ELIgcfbXpUbgavFZm6RG4EvF\ 180 | Zm6RG4EvFZm6RG4FGkTaHmXpUbgVeJzP82jcCXjB9aBzvvI3Aq8QJg8x/N/ZRuBg0iTY22NvxP2B\ 181 | RuAM0iMwf9f7sUbgYPIc+MDssszL9yjcDBpDhb51j8j+4FG4AVkhx9hbbcwiP1UUmwqukIPC/boX\ 182 | b9QFJ2AmukAfhWt0Ft7FijsBZZT8a30I2/8AH7ew4o7AzeANwodTviZHYEvAG4UOp3xMjsCt19HL\ 183 | g/qd8TI7Am6++0/qd8To7Am6++0/qd8To7ASiOn4CORpOMHjYCu30Pcn3nS6HzKq8lWRinc8Gc0K\ 184 | GKIKiWgDgHDhNylTHZHf9B9ekFwrKl0sJ5AbciDOXfweTtJbzNxQ9dMV+x6XUXRqDMH5Odmc+HcH\ 185 | YpNAwXQt2LVYqmWRqlWcciY72ZxAOYViKP8AdJM8Fbv2biPsemBNbpNA7PX0IGwSeY8b2RJqCXvw\ 186 | HFV0NaGYN8k4xjnvMNRSi1l0pbVAWhJVhGzlsOooCx7qQ81AWkka7xHkvg2pICC+60RApHfJFOzm\ 187 | vg+pICDXde4vUjYqkc6AwZb1WD/rSdCSq7JRYxNG1nnId1tKkAs2iqfJ1Y25Q4oVRuslEaLtY3K8\ 188 | jTQxdEw62QANF2vfJQtLTgwBnREOtkBRZdsolD83T2wF0f8ABkAC+7YxgGQkKwBnPzwdARartpHq\ 189 | ppBWSSDAACHs14OgHd92zjlfvyyRrCOjng6A6pu2cWWWmkA7zZ7+HW6A0z3bKI1g2Hp5YBw8MI63\ 190 | QGVF2te58lTew9Eg62QG2W7UNa7J00MfRQbWyA0zXZ/bgLFlMjN0VjrVAZV3ZJjW5OmNXzIu8DQD\ 191 | q7sG61gABqZC4cf8LS6yQGAutziXCGKpmszosV6wQCkuqsPrf1bRLgyopVawQChdQodPbbTkPRMo\ 192 | 1ggUe6aQ4fJU7dUTNsegLrbpJCq3JU7jp8xj13agJrbo3Bxw9Lr9Mu8exaAse6MwWYuKCnIA0Wdo\ 193 | 9ikDO9GIM43Mumrt2JQJvRiDONzLpq7diUAZro7BID6XH6XdmxKBW9HYJ43D6XdmxCBN6OwTxuH0\ 194 | u7NiEBCKbo5BAuFpLuuI5DKe7s9X3oRP0PmXV5VxCD6na3vAsmtogdSTyMGp2jZhNl7jATmP/9k="\ 195 | ) no-repeat scroll 0 0 transparent; }' ); 196 | 197 | -------------------------------------------------------------------------------- /source/scripts/attic/samPresets.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Quake Live Start-a-Match quick preset access 3 | // @description Adds links to load your saved presets directly from the start-a-match screen 4 | // @author PredatH0r 5 | // @version 1.2 6 | // @include http://*.quakelive.com/* 7 | // @exclude http://*.quakelive.com/forum* 8 | // @unwrap 9 | // ==/UserScript== 10 | 11 | /* 12 | 13 | Version 1.2 14 | - updated to work with latest UI changes 15 | 16 | Version 1.1 17 | - updated extraQL script url to sourceforge 18 | 19 | */ 20 | 21 | (function() { 22 | var amplify = window.amplify; 23 | var quakelive = window.quakelive; 24 | 25 | function init() { 26 | extraQL.addStyle( 27 | "#quickSamPresets { background-color: #AAA; color: black; padding: 3px 15px; }", 28 | "#quickSamPresets a { color: black; }" 29 | ); 30 | quakelive.AddHook("OnContentLoaded", onContentLoaded); 31 | onContentLoaded(); 32 | } 33 | 34 | function onContentLoaded() { 35 | if (!quakelive.activeModule || quakelive.activeModule.GetTitle() != "Create Match") 36 | return; 37 | if ($("#quickSamPresets").length > 0) 38 | return; 39 | 40 | var html = "
    "; 41 | var presets = amplify.store("sam_presets") || {}; 42 | var count = 0; 43 | $.each(presets, function(name) { 44 | html += count++ == 0 ? "Load preset: " : " | "; 45 | html += "" + extraQL.escapeHtml(name) + ""; 46 | }); 47 | if (count == 0) 48 | html += "No saved presets available"; 49 | html += "
    "; 50 | $("#mod_sam").prepend(html); 51 | $("#quickSamPresets a").click(function () { quakelive.mod_startamatch.loadPreset($(this).text()); }); 52 | } 53 | 54 | init(); 55 | })(window); 56 | 57 | -------------------------------------------------------------------------------- /source/scripts/attic/specHelper.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 153714 3 | // @name QuakeLive Speccing Helper 4 | // @version 1.1 5 | // @description Autoexec commands on spectator connect, reconnect on errors 6 | // @namespace qlspec 7 | // @homepage http://esreality.com 8 | // @author simonov 9 | // @contributor wn 10 | // ==/UserScript== 11 | 12 | // Don't bother if Quake Live is down for maintenance 13 | if (/offline/i.test(document.title)) { 14 | return; 15 | } 16 | 17 | // Make sure we're on top 18 | if (window.self != window.top) { 19 | return; 20 | } 21 | 22 | // Set up some utility things 23 | var DEBUG = true, 24 | DOLITTLE = function() {}, 25 | logMsg = DEBUG ? function(aMsg) { 26 | console.log("QLSPEC: " + aMsg) 27 | } : DOLITTLE, 28 | logError = function(aMsg) { 29 | console.log("QLSPEC ERROR: " + aMsg) 30 | }; 31 | 32 | /** 33 | * Override OnCvarChanged 34 | */ 35 | QLSPEC = { 36 | // Debug 37 | DEBUG: false, 38 | 39 | // cache 40 | VARS: {}, 41 | TIMES: {}, 42 | 43 | // vars 44 | TIMEOUT: 10000, // timeout to check for connection error 45 | } 46 | 47 | // make a spec actions 48 | QLSPEC.do = function(cvar, delay) { 49 | // do spectator actions only if asked to 50 | if (quakelive.cvars.GetIntegerValue("_qlspec_active", 0) != 1) { 51 | return; 52 | } 53 | 54 | logMsg("spectating commands via " + cvar); 55 | qz_instance.SendGameCommand('vstr _qlspec_do;'); 56 | 57 | // change pov to make sure we are spectating a person, not a wall 58 | window.setTimeout(function() { 59 | qz_instance.SendGameCommand('vstr _qlspec_pov;'); 60 | }, delay); 61 | 62 | // change pov to next player in a given period of time 63 | var next_pov_time = quakelive.cvars.GetIntegerValue("_qlspec_nextpovtime", 0); 64 | if (next_pov_time > 0) { 65 | window.setTimeout(function() { 66 | qz_instance.SendGameCommand('vstr _qlspec_pov;'); 67 | }, next_pov_time * 1000); 68 | } 69 | } 70 | 71 | var oldOnCvarChanged = OnCvarChanged; 72 | OnCvarChanged = function(name, val, replicate) { 73 | //logMsg(name + " = " + val); 74 | var timeout = +new Date() - QLSPEC.TIMEOUT; 75 | switch (name) { 76 | case "com_errorMessage": 77 | QLSPEC.VARS[name] = val; 78 | QLSPEC.TIMES[name] = +new Date(); 79 | break; 80 | 81 | case "sv_cheats": 82 | // sv_cheats goes to 1 after appearing of an error message 83 | if (qz_instance.IsGameRunning() && val == 1 && typeof(QLSPEC.TIMES["com_errorMessage"]) != "undefined" && QLSPEC.TIMES["com_errorMessage"] - timeout > 0) { 84 | if ( 85 | // manual disconnect from server 86 | (QLSPEC.VARS["com_errorMessage"].indexOf("Disconnected from server") >= 0) 87 | // kicked from server 88 | || (QLSPEC.VARS["com_errorMessage"].indexOf(" kicked") >= 0) 89 | ) { 90 | logMsg('kicked or disconnected, no need to reconnect'); 91 | break; 92 | } 93 | logMsg('reconnecting'); 94 | qz_instance.SendGameCommand('reconnect;'); 95 | } else if (val == 0) { 96 | // game started 97 | QLSPEC.VARS[name] = val; 98 | QLSPEC.TIMES[name] = +new Date(); 99 | if (quakelive.cvars.GetIntegerValue("cg_spectating") == 1) { 100 | QLSPEC.do(name, 5000); 101 | } 102 | } 103 | break; 104 | 105 | case "cg_spectating": 106 | if (qz_instance.IsGameRunning() && val == 1 && typeof(QLSPEC.TIMES["sv_serverid"]) != "undefined" && QLSPEC.TIMES["sv_serverid"] - timeout > 0) { 107 | QLSPEC.do(name, 1000); 108 | } 109 | break; 110 | 111 | case "sv_serverid": 112 | case "gt_eventid": 113 | QLSPEC.VARS[name] = val; 114 | QLSPEC.TIMES[name] = +new Date(); 115 | if (qz_instance.IsGameRunning() && quakelive.cvars.GetIntegerValue("cg_spectating") == 1) { 116 | QLSPEC.do(name, 1000); 117 | } 118 | break; 119 | } 120 | oldOnCvarChanged.call(null, name, val, replicate); 121 | } 122 | -------------------------------------------------------------------------------- /source/scripts/attic/startpage.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id startpage 3 | // @name Start Page 4 | // @description Opens QL with your preferred start page. Set any page as your start page with "Userscripts / Set Start Page" 5 | // @author PredatH0r 6 | // @version 1.0 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | (function () { 11 | function init() { 12 | var home = quakelive.cvars.Get("web_home"); 13 | if (home && home.value) 14 | window.location.href = "/#!" + home.value; 15 | 16 | HOOK_MANAGER.addMenuItem("Set as Start Page", setStartPage); 17 | } 18 | 19 | function setStartPage() { 20 | var page = window.location.hash; 21 | if (page.length > 2) { 22 | page = page.substr(2); 23 | quakelive.cvars.Set("web_home", page); 24 | } 25 | } 26 | 27 | init(); 28 | })(); -------------------------------------------------------------------------------- /source/scripts/attic/streamNotifier.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 114449 3 | // @name Quake Live Stream Notifier 4 | // @version 0.67 5 | // @namespace phob.net 6 | // @author wn 7 | // @contributor cityy 8 | // @description Displays a list of live gaming streams on QuakeLive.com and ESReality.com 9 | // @include http://esreality.com/* 10 | // @include http://*.esreality.com/* 11 | // @exclude http://*.esreality.com/w* 12 | // @include http://esreality.net/* 13 | // @include http://*.esreality.net/* 14 | // @include http://*.quakelive.com/* 15 | // @exclude http://*.quakelive.com/forum* 16 | // @run-at document-end 17 | // @updateURL https://userscripts.org/scripts/source/114449.meta.js 18 | // ==/UserScript== 19 | 20 | 21 | /** 22 | * Only run if we're in the top frame 23 | */ 24 | if (window.self != window.top) { 25 | return; 26 | } 27 | 28 | 29 | /** 30 | * Global stuff 31 | */ 32 | var DOLITTLE = function() {} 33 | , GM_registerMenuCommand = GM_registerMenuCommand ? GM_registerMenuCommand : DOLITTLE 34 | , UPDATE_MS = 3E5 // 5 * 60 * 1000 35 | , RE_okayChars = /^[\w .,`~!@#&*:\/\\\-=+?{}\(\)\[\]\|]+$/ 36 | , RE_url = /^[\w .:\/\\\-=+?#&]+$/ 37 | , isESR = /^http:\/\/(?:\w+\.)?esreality\.(?:com|net)\/.*$/i.test(window.location.href) 38 | , src = "http://phob.net/qlsn_quake_streams.json" 39 | , jsonReq 40 | ; 41 | 42 | 43 | /** 44 | * Helper to add CSS 45 | */ 46 | function addStyle(aContent) { 47 | if ("[object Array]" === Object.prototype.toString.call(aContent)) 48 | aContent = aContent.join("\n"); 49 | var s = document.createElement("style"); 50 | s.type = "text/css"; 51 | s.textContent = aContent; 52 | document.body.appendChild(s); 53 | } 54 | 55 | 56 | /** 57 | * Add the live stream bar 58 | */ 59 | addStyle( 60 | "#qlsn_bar {margin: 0 auto; padding: 2px; background-color: " 61 | + (localStorage["qlsn_barColor"] || "#000") + "; clear: both;}" 62 | + "#qlsn_bar em {color: #ccc; font-style: italic;}" 63 | + "#qlsn_bar strong {font-weight: bold;}" 64 | + "#qlsn_bar a {text-decoration: none;}" 65 | + "#qlsn_bar a.qlsn_stream {color: #ccc;}" 66 | + "#qlsn_bar a.qlsn_stream:hover {color: #fff;}" 67 | ); 68 | 69 | // Styling unique to ESR or QL 70 | if (isESR) 71 | addStyle("#qlsn_bar {font-size: 11px;}"); 72 | else 73 | addStyle("#qlsn_bar strong, #qlsn_bar strong a {color: #fc0;}"); 74 | 75 | var qlsnBar = document.createElement("div"); 76 | qlsnBar.setAttribute("id", "qlsn_bar"); 77 | qlsnBar.innerHTML = "loading live streams…"; 78 | document.body.insertBefore(qlsnBar, document.body.firstChild); 79 | 80 | 81 | /** 82 | * Potential sources in order of priority 83 | * NOTE: Intentionally duplicated the source so we try it again if the first 84 | * request fails, since it is quite likely something other than site 85 | * downtime caused the failure. 86 | */ 87 | var listSources = [src, src]; 88 | 89 | 90 | /** 91 | * Updates the live stream bar 92 | * @param {Array} an array of live streams 93 | */ 94 | function updateBar(liveStreams) { 95 | var x = [] 96 | , bar = document.getElementById("qlsn_bar") 97 | ; 98 | 99 | // Stop if we don't have any live streams 100 | if (!liveStreams.length) { 101 | bar.innerHTML = "no live streams found"; 102 | return; 103 | } 104 | 105 | // Sort by stream name 106 | liveStreams.sort(function(a, b) { 107 | a = a.name.toLowerCase(), b = b.name.toLowerCase(); 108 | return (a < b ? -1 : a > b ? 1 : 0); 109 | }); 110 | 111 | // Generate and display the list 112 | for (var i = 0, e = liveStreams.length; i < e; ++i) { 113 | if (!(RE_okayChars.test(liveStreams[i].name) 114 | && RE_okayChars.test(liveStreams[i].game) 115 | && RE_url.test(liveStreams[i].url) 116 | && !isNaN(parseInt(liveStreams[i].viewers)))) { 117 | console.log("Unexpected value(s) in: " + JSON.stringify(liveStreams[i])); 118 | continue; 119 | } 120 | x.push("" 124 | + liveStreams[i].name + ""); 125 | } 126 | 127 | if (0 == x.length) { 128 | x = ["(error parsing stream list)"]; 129 | } 130 | 131 | bar.innerHTML = "LIVE NOW:  " 133 | + x.join("  |  "); 134 | } 135 | 136 | 137 | /** 138 | * Get a new list of live streams. 139 | * Tries the fallback source if the first results in an error. 140 | * @param {Integer} an optional index specifying the list source to try 141 | * @param {Boolean} an optional flag to force a refresh 142 | */ 143 | function refreshList(aIndex, aForce) { 144 | // If we're within UPDATE_MS of the last good refresh, try to load from cache. 145 | // Currently only doing this for ESR, since QL doesn't actually reload often. 146 | var liveStreams 147 | , now = (new Date()).getTime() 148 | , fetchCache = localStorage["qlsn_fetchCache"] 149 | , lastFetch = Number(localStorage["qlsn_lastFetch"]) 150 | ; 151 | 152 | if (isESR && !aForce && fetchCache && lastFetch 153 | && (now - lastFetch < UPDATE_MS)) { 154 | try { 155 | fetchCache = JSON.parse(fetchCache); 156 | updateBar(fetchCache); 157 | return; 158 | } 159 | catch(e) {} 160 | } 161 | 162 | // Starting fresh 163 | liveStreams = []; 164 | localStorage["qlsn_fetchCache"] = ""; 165 | 166 | // Default to the first source if not specified 167 | aIndex = aIndex || 0; 168 | 169 | // Make sure the index specified is valid, otherwise stop trying 170 | if ("undefined" == typeof listSources[aIndex]) { 171 | updateBar(liveStreams); 172 | return; 173 | } 174 | 175 | // Try updating with the current list source. 176 | // If it fails, retry using the next one. 177 | jsonReq = new XMLHttpRequest(); 178 | jsonReq.onreadystatechange = function() { 179 | // Done? 180 | if (jsonReq.readyState === 4) { 181 | // Good HTTP response? 182 | if (200 == jsonReq.status) { 183 | try { 184 | liveStreams = JSON.parse(jsonReq.responseText); 185 | } 186 | catch(e) { 187 | console.log("Error parsing response from " + listSources[aIndex] 188 | + " (" + e + ")"); 189 | refreshList(aIndex+1, aForce); 190 | return; 191 | } 192 | localStorage["qlsn_fetchCache"] = JSON.stringify(liveStreams); 193 | localStorage["qlsn_lastFetch"] = now; 194 | updateBar(liveStreams); 195 | } 196 | // Bad HTTP response... 197 | else { 198 | console.log("Error requesting JSON from " + listSources[aIndex] 199 | + " (" + jsonReq.statusText + ")"); 200 | refreshList(aIndex+1, aForce); 201 | } 202 | } 203 | } 204 | jsonReq.open("GET", listSources[aIndex] + "?" + now, true) 205 | jsonReq.send(); 206 | } 207 | 208 | 209 | /** 210 | * Add a menu command to change the bar's background color 211 | */ 212 | GM_registerMenuCommand("Quake Live Stream Notifier: Change the bar color", function() { 213 | var color = prompt("Enter a CSS-friendly color without quotation marks.\n\n" 214 | + "For example (without quotation marks): 'red' OR '#621300'\n\n" 215 | , (localStorage["qlsn_barColor"] || "#000")); 216 | if (!color) return; 217 | 218 | localStorage["qlsn_barColor"] = color; 219 | alert("Value set. Please reload Quake Live."); 220 | }); 221 | 222 | 223 | /** 224 | * Add a menu command to force a list refresh 225 | */ 226 | GM_registerMenuCommand("Quake Live Stream Notifier: Force list reload", function() { 227 | refreshList(null, true); 228 | }); 229 | 230 | 231 | /** 232 | * Update the list every UPDATE_MS 233 | */ 234 | window.setInterval(function(){refreshList()}, UPDATE_MS); 235 | 236 | 237 | /** 238 | * Load the list for the first time 239 | */ 240 | refreshList(); 241 | -------------------------------------------------------------------------------- /source/scripts/attic/toolTip.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 395655 3 | // @name Quake Live ToolTip Fix 4 | // @description Shows a ToolTip popup when hovering the mouse over a UI element with additional information. E.g. QLranks Elo information, weapon accuracy, ... 5 | // @author PredatH0r 6 | // @version 1.3 7 | // @unwrap 8 | // ==/UserScript== 9 | 10 | /* 11 | 12 | Description: 13 | If you hover your mouse over UI elements which have additional information, that info 14 | will be shown in a ToolTip popup. 15 | 16 | This standard web browser feature is somehow broken in the QuakeLive standalone client. 17 | This script takes care of displaying this information text. 18 | 19 | e.g. Server modification, QLranks Elo information, weapon accuracy, ... 20 | 21 | Version 1.3: 22 | - Fixed ToolTip position when window is scrolled 23 | - ToolTip now activated, when QL is started fullscreen 24 | - ToolTip is now showing on top of other elements (like player list popup) 25 | 26 | Version 1.2.1: 27 | Fixed issue with empty ToolTip text 28 | 29 | Version 1.2: 30 | This alternative ToolTip is activated only after game-mode was entered, which is what breaks the QL built-in 31 | tool tips. The alternative ToolTip can manually be enabled/disabled with cvar web_tooltip. 32 | 33 | Version 1.1: 34 | Rewritten based on tips from 'wn'. 35 | 36 | Version 1.0: 37 | First release 38 | 39 | */ 40 | 41 | (function () { // scope 42 | var $tooltip; 43 | var enabled = false; 44 | var oldOnCvarChanged; 45 | 46 | function init() { 47 | $("head").append( 48 | ''); 51 | 52 | $("body").append('
    '); 53 | $tooltip = $("#ttip"); 54 | 55 | // activate the ToolTip when it was loaded through the QLHM UI or we are in fullscreen mode 56 | if ($("#qlhmPrompt").length > 0 || quakelive.cvars.Get("r_fullscreen").value == "1") 57 | enable(); 58 | else 59 | disable(); 60 | 61 | // install hook to react on changed cvar "web_tooltip" 62 | oldOnCvarChanged = window.OnCvarChanged; 63 | window.OnCvarChanged = OnCvarChanged; 64 | 65 | // The standard QL tool-tip implementation breaks when game mode is started. 66 | // So when game mode is started, activate the alternative tool tip 67 | quakelive.AddHook("OnGameModeStarted", enable); 68 | } 69 | 70 | function OnCvarChanged(name, val, replicate) { 71 | try { 72 | var lower = name.toLowerCase(); 73 | if (lower == "web_tooltip") { 74 | if (val == "0") 75 | disable(); 76 | else 77 | enable(); 78 | } 79 | } 80 | catch (e) { } 81 | oldOnCvarChanged.call(null, name, val, replicate); 82 | } 83 | 84 | function enable() { 85 | quakelive.cvars.Set("web_tooltip", 1); 86 | if (!enabled) { 87 | enabled = true; 88 | $("body").on("hover", "[title]", onHover); 89 | } 90 | } 91 | 92 | function disable() { 93 | quakelive.cvars.Set("web_tooltip", 0); 94 | if (enabled) { 95 | enabled = false; 96 | $("body").off("hover", "[title]", onHover); 97 | hideTip(); 98 | } 99 | } 100 | 101 | function onHover(e) { 102 | if ("mouseenter" === e.type) 103 | showTip(e.pageX, e.pageY, this.title); 104 | else 105 | hideTip(); 106 | } 107 | 108 | function showTip(x, y, text) { 109 | $tooltip.empty(); 110 | $tooltip.append(text); 111 | if (text && text.trim() != "") 112 | $tooltip.css({ "left": x + 16, "top": y + 16, "display": "block" }); 113 | } 114 | 115 | function hideTip() { 116 | $tooltip.css("display", "none"); 117 | } 118 | 119 | init(); 120 | })(window); 121 | 122 | -------------------------------------------------------------------------------- /source/scripts/attic/twitter.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name QL Twitter integration 3 | // @version 1.2 4 | // @author PredatH0r 5 | // @description Show @quakelive tweets in chat bar 6 | // @unwrap 7 | // ==/UserScript== 8 | 9 | /* 10 | 11 | This script integrates the official "@quakelive" Twitter channel as a separate tab in the QL chat window. 12 | If the "QL Chat Dock" script is also installed, the Twitter popup will automatically adjust to the QL window's size. 13 | 14 | Version 1.2 15 | - ensuring consistent order of tabs in the chat bar 16 | 17 | Version 1.1 18 | - updated extraQL script url to sourceforge 19 | 20 | Version 1.0 21 | - first public release 22 | 23 | */ 24 | 25 | (function () { 26 | // external variables 27 | var quakelive = window.quakelive; 28 | var extraQL = window.extraQL; 29 | 30 | function init() { 31 | // delay init so that twitch, twitter, ESR and IRC scripts add items to chat menu bar in a defined order 32 | if (extraQL.hookVersion) // introduced at the same time as the addTabPage() "priority" param 33 | delayedInit(); 34 | else 35 | setTimeout(delayedInit, 800); 36 | } 37 | 38 | function delayedInit() { 39 | onContentLoaded(); 40 | quakelive.AddHook("OnContentLoaded", onContentLoaded); 41 | 42 | // the resizeLayout script's event handler will resize the
    for us 43 | if (typeof (window.onresize) == "function") 44 | window.onresize(); 45 | } 46 | 47 | function onContentLoaded() { 48 | if ($("#twitterFeed").length) 49 | return; 50 | 51 | extraQL.addStyle("#twitterFeed { width: 300px; background-color: white; display: none; }"); 52 | 53 | var html = 54 | '' + 55 | ''; 56 | 57 | var page = "
    " + html + "
    "; 58 | extraQL.addTabPage("twitterFeed", "Twitter", page, showTwitterTab, 200); 59 | } 60 | 61 | function showTwitterTab() { 62 | $("#twitter-widget-0").addClass("fullHeight"); // this enables automatic resizing via resizeLayout.usr.js 63 | extraQL.showTabPage("twitterFeed"); 64 | } 65 | 66 | init(); 67 | })(); 68 | -------------------------------------------------------------------------------- /source/scripts/attic/weightedAcc.usr.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @id 99947 3 | // @name Quake Live Weighted Accuracy 4 | // @namespace http://userstyles.org 5 | // @description A better accuracy measurement for QuakeLive. 6 | // @include http://*.quakelive.com/* 7 | // @include http://*.quakelive.com/profile/statistics/* 8 | // ==/UserScript== 9 | 10 | (function () { 11 | return; // this script is broken as of 2014-04-28 12 | 13 | function weightedacc(event) { 14 | var totalweightedacc = 0; // The final result of the calculation. 15 | //var reloadtimes = [400, 100, 1000, 800, 800, 50, 1500, 100, 300, 50, 1000, 800]; // Weapon reload times (msec). 16 | //var totalreloadtime = reloadtimes.reduce(function(a, b){ return a+b;}); // Calculate the total of the reload times. 17 | //var averagereloadtime = totalreloadtime/reloadtimes.length; 18 | 19 | // Make sure the proper type of element is being inserted, and this hasn't already been called on this page. 20 | if (event.target.tagName != "DIV" 21 | && event.target.className != 'prf_weapons' 22 | || document.getElementById('weightedacc')) { 23 | return; 24 | } 25 | 26 | // Calculate a new, "weighted" accuracy. 27 | var weapselem = document.getElementsByClassName('prf_weapons cl')[0]; 28 | var accelems = weapselem.getElementsByClassName('text_tooltip'); // userscripts.org/topics/77188 29 | var useelems = weapselem.getElementsByClassName('col_usage'); 30 | 31 | var i; 32 | for (i = 0; i < accelems.length; i++) { // Foreach weapon, ignore gaunt. 33 | acc = accelems[i].innerHTML.substr(0, accelems[i].innerHTML.length - 1) * 1; 34 | use = useelems[i + 1].innerHTML.substr(0, useelems[i + 1].innerHTML.length - 1) * 1; // userscripts.org/topics/77188 35 | 36 | totalweightedacc += (acc * use/100); 37 | //totalweightedacc += (acc * reloadtimes[i]/averagereloadtime); 38 | } 39 | 40 | // Display this new value. 41 | var vitals = document.getElementsByClassName('prf_vitals')[0]; 42 | var info = vitals.getElementsByTagName('p')[0]; 43 | info.innerHTML += "Weighted Accuracy: " 44 | + totalweightedacc.toFixed(1) + "%
    "; 45 | } 46 | 47 | document.body.addEventListener("DOMNodeInserted", weightedacc, true); 48 | 49 | })(); -------------------------------------------------------------------------------- /source/scripts/autoExec.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AutoExec: Automate actions when you start/leave a match. 3 | // @version 2.1 4 | // @author PredatH0r 5 | // @description Toggles fullscreen and executes commands when you start/leave a game. 6 | // @description /r_autoFullscreen: 1=fullscreen when playing, 2=windowed when not, 3=both. 7 | // @description gamestart.cfg and gameend.cfg are executed when joining/leaving a match. 8 | // @description You can use /steamnick in these config files to add color to your name. 9 | // @enabled 1 10 | // ==/UserScript== 11 | 12 | /* 13 | 14 | Version 2.1 15 | - create r_autoFullscreen cvar if not set 16 | 17 | Version 2.0 18 | - rewrite to work with Steam exclusive version of Quake Live 19 | 20 | */ 21 | 22 | (function () { 23 | // external global variables 24 | var qz_instance = window.qz_instance; 25 | var console = window.console; 26 | var lastGameEndTimestamp; 27 | 28 | function init() { 29 | // create cvar 30 | var autoFullscreen = qz_instance.GetCvar("r_autoFullscreen"); 31 | if (!autoFullscreen) 32 | qz_instance.SetCvar("r_autoFullscreen", "0"); 33 | 34 | var postal = window.req("postal"); 35 | var channel = postal.channel(); 36 | channel.subscribe("game.start", onGameStart); 37 | channel.subscribe("game.end", onGameEnd); 38 | echo("^2autoExec.js installed"); 39 | } 40 | 41 | function log(msg) { 42 | console.log(msg); 43 | } 44 | 45 | function echo(msg) { 46 | msg = msg.replace(/\"/g, "'").replace(/[\r\n]+/g, " "); 47 | qz_instance.SendGameCommand("echo \"" + msg + "\""); 48 | } 49 | 50 | function onGameStart() { 51 | echo("^3autoExec.js: executing gamestart.cfg"); 52 | qz_instance.SendGameCommand("exec gamestart.cfg"); 53 | autoSwitchFullscreen(1); 54 | } 55 | 56 | function onGameEnd() { 57 | var now = Date.now(); 58 | 59 | // QL sends 2x "game.end" notifications and r_fullscreen is latched between the two, so we don't know the real state 60 | // this hack ignores the 2nd if it's within 2 seconds of the first 61 | if (lastGameEndTimestamp + 2000 > now) 62 | return; 63 | lastGameEndTimestamp = now; 64 | 65 | echo("^3autoExec.js: executing gameend.cfg"); 66 | qz_instance.SendGameCommand("exec gameend.cfg"); 67 | autoSwitchFullscreen(0); 68 | } 69 | 70 | function autoSwitchFullscreen(enter) { 71 | var auto = qz_instance.GetCvar("r_autoFullscreen"); 72 | var mask = enter ? 0x01 : 0x02; 73 | if (auto != "" && (parseInt(auto) & mask) && qz_instance.GetCvar("r_fullscreen") != enter) { 74 | //log("autoExec.js: switching to " + (enter ? "fullscreen" : "windowed mode")); 75 | extraQl_SetFullscreen(enter); 76 | 77 | // extraQL may not be running or QL might have ignored the Alt+Enter keypress (when disconnecting), so check again after a timeout 78 | setTimeout(1000, function () { 79 | if (qz_instance.GetCvar("r_fullscreen") != enter) 80 | qz_instance.SendGameCommand("set r_fullscreen " + mode + "; vid_restart"); 81 | }); 82 | } 83 | } 84 | 85 | function extraQl_SetFullscreen(mode) { 86 | var xhttp = new XMLHttpRequest(); 87 | xhttp.timeout = 1000; 88 | xhttp.onload = function () { 89 | if (xhttp.status == 200) { 90 | log("autoExec.js: switched r_fullscreen=" + mode + " through extraQL.exe"); 91 | } else 92 | log("autoExec.js: failed to switch r_fullscreen=" + mode + " through extraQL.exe: " + xhttp.responseText); 93 | } 94 | xhttp.onerror = function () { echo("^3extraQL.exe not running:^7 run extraQL.exe to prevent a map-reload when switching fullscreen"); } 95 | xhttp.open("GET", "http://localhost:27963/toggleFullscreen?mode=" + mode, true); 96 | xhttp.send(); 97 | } 98 | 99 | 100 | // there is a race condition between QL's bundle.js and the userscripts. 101 | // if window.req was published by bundle.js, we're good to go. 102 | // otherwise add a callback to main_hook_v2, which will be called by bundle.js later 103 | if (window.req) 104 | init(); 105 | else { 106 | var oldHook = window.main_hook_v2; 107 | window.main_hook_v2 = function() { 108 | if (typeof oldHook == "function") 109 | oldHook(); 110 | init(); 111 | } 112 | } 113 | })(); 114 | 115 | -------------------------------------------------------------------------------- /source/scripts/instaBounce.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name InstaBounce: Automate special config with key binds for InstaBounce gametype 3 | // @version 1.1 4 | // @author PredatH0r 5 | // @description InstaBounce is InstaGib + 0-Damage-Bounce-Rockets + Grappling hook. 6 | // @description This script automatically detects this game type and executes the config files 7 | // @description ibounce_on.cfg and ibounce_off.cfg to setup aliases and key binds. 8 | // @description The aliases +hook and +rock allow you to instantly switch and fire weapons. 9 | // @description Your config is restored when you leave an InstaBounce server. 10 | // @enabled 1 11 | // ==/UserScript== 12 | 13 | /* 14 | 15 | Version 1.1 16 | - fixed issues with restoring original config 17 | 18 | Version 1.0 19 | - initial release 20 | 21 | */ 22 | 23 | (function () { 24 | // external global variables 25 | var qz_instance = window.qz_instance; 26 | var console = window.console; 27 | 28 | // constants 29 | var CVAR_DisableMsg = "cg_disableInstaBounceBindMsg"; 30 | var CVAR_InstaBounce = "cg_instaBounce"; 31 | var IB_DISABLED = "-1"; 32 | var IB_AUTODETECT = "0"; 33 | var IB_ACTIVE = "1"; 34 | 35 | // internal variables 36 | var ignoreEvents = false; 37 | var mainMenuTimer; 38 | 39 | 40 | function init() { 41 | restoreNormalConfig(); // in case someone used /quit and the normal config wasn't restored 42 | var postal = window.req("postal"); 43 | var channel = postal.channel(); 44 | channel.subscribe("cvar.ui_mainmenu", onUiMainMenu); // used to detect in-game vs. menu-mode 45 | channel.subscribe("cvar.cg_spectating", onSpectating); // wait for the player to join so he can see the "print" message 46 | var ib = qz_instance.GetCvar(CVAR_InstaBounce); 47 | var status = ib == IB_AUTODETECT || ib == IB_ACTIVE ? "auto-detect" : "disabled"; 48 | echo("^2instaBounce.js installed^7 (" + CVAR_InstaBounce + "=" + ib + ": " + status + ")"); 49 | } 50 | 51 | function log(msg) { 52 | console.log(msg); 53 | } 54 | 55 | function echo(msg) { 56 | msg = msg.replace(/\"/g, "'").replace(/[\r\n]+/g, " "); 57 | qz_instance.SendGameCommand("echo \"" + msg + "\""); 58 | } 59 | 60 | function onUiMainMenu(data) { 61 | if (ignoreEvents) return; 62 | 63 | // When changing maps, ui_mainMenu gets set to 1 and back to 0. 64 | // To prevent unnecessary config resetting and console flooding, we use a timeout 65 | if (data.value == "0") { 66 | if (mainMenuTimer) 67 | clearTimeout(mainMenuTimer); 68 | checkFactoryForInstaBounce(); 69 | } 70 | else { 71 | mainMenuTimer = setTimeout(restoreNormalConfig, 1000); 72 | } 73 | } 74 | 75 | function onSpectating(data) { 76 | if (ignoreEvents) return; 77 | if (parseInt(data.value) == 0 && qz_instance.GetCvar(CVAR_InstaBounce) == IB_ACTIVE) 78 | showKeyBindMessage(); 79 | } 80 | 81 | function restoreNormalConfig() { 82 | mainMenuTimer = undefined; 83 | if (qz_instance.GetCvar(CVAR_InstaBounce) != IB_ACTIVE) 84 | return; 85 | ignoreEvents = true; 86 | qz_instance.SendGameCommand("exec ibounce_off.cfg"); 87 | ignoreEvents = false; 88 | qz_instance.SetCvar(CVAR_InstaBounce, "1"); 89 | echo("^3instaBounce.js:^7 restored normal config (ibounce_off.cfg)"); 90 | } 91 | 92 | function checkFactoryForInstaBounce() { 93 | if (qz_instance.GetCvar(CVAR_InstaBounce) == IB_DISABLED) 94 | return; 95 | qz_instance.SendGameCommand("serverinfo"); 96 | qz_instance.SendGameCommand("condump extraql_condump.txt"); 97 | setTimeout(function() { 98 | var xhttp = new XMLHttpRequest(); 99 | xhttp.timeout = 1000; 100 | xhttp.onload = function() { extraQLCondumpOnLoad(xhttp); } 101 | xhttp.onerror = function() { echo("^3extraQL.exe not running:^7 run extraQL.exe to auto-exec ibounce_on/off.cfg when connecting to (non-)InstaBounce servers."); } 102 | xhttp.open("GET", "http://localhost:27963/condump", true); 103 | xhttp.send(); 104 | }, 100); 105 | } 106 | 107 | function extraQLCondumpOnLoad(xhttp) { 108 | var isInstaBounce = false; 109 | if (xhttp.status == 200) { 110 | var condump = xhttp.responseText; 111 | var idx, idx2; 112 | if ((idx = condump.lastIndexOf("g_factoryTitle")) >= 0 && (idx2 = condump.indexOf("\n", idx)) >= 0) { 113 | var factory = condump.substr(idx + 14, idx2 - idx - 14).trim(); 114 | if (factory.indexOf("InstaBounce") >= 0) 115 | isInstaBounce = true; 116 | } 117 | else { 118 | log("cound not detect g_factory in condump"); 119 | } 120 | } 121 | else 122 | log("^1instaBounce.js:^7 failed to load serverinfo/condump through extraQL.exe: " + xhttp.responseText); 123 | 124 | if (isInstaBounce) { 125 | if (qz_instance.GetCvar(CVAR_InstaBounce) == IB_AUTODETECT) { 126 | qz_instance.SendGameCommand("writeconfig ibounce_off.cfg"); // backup current config 127 | // writeconfig doesn't write immediately, so we have to defer the config changes 128 | setTimeout(function() { 129 | qz_instance.SendGameCommand("exec ibounce_on.cfg"); 130 | qz_instance.SetCvar(CVAR_InstaBounce, IB_ACTIVE); 131 | echo("^3instaBounce.js:^7 activated InstaBounce config (ibounce_on.cfg)"); 132 | }, 1000); 133 | } 134 | } 135 | else 136 | restoreNormalConfig(); 137 | } 138 | 139 | function showKeyBindMessage() { 140 | if (qz_instance.GetCvar(CVAR_DisableMsg) == "1") 141 | return; 142 | var msg = "^3Edit^7 Quake Live//baseq3/^3ibounce_on.cfg^7 to ^5bind +hook^7 and ^5+rock^7 to your preferred keys/buttons and disable this message."; 143 | qz_instance.SendGameCommand("echo \"" + msg + "\""); 144 | qz_instance.SendGameCommand("print \"" + msg + "\""); 145 | msg = "^7Your original config will be restored automatically. You can also force it with ^5exec ibounce_off.cfg^7."; 146 | qz_instance.SendGameCommand("echo \"" + msg + "\""); 147 | } 148 | 149 | 150 | // there is a race condition between QL's bundle.js and the userscripts. 151 | // if window.req was published by bundle.js, we're good to go. 152 | // otherwise add a callback to main_hook_v2, which will be called by bundle.js later 153 | if (window.req) 154 | init(); 155 | else { 156 | var oldHook = window.main_hook_v2; 157 | window.main_hook_v2 = function () { 158 | if (typeof oldHook == "function") 159 | oldHook(); 160 | init(); 161 | } 162 | } 163 | })(); 164 | 165 | -------------------------------------------------------------------------------- /source/scripts/postalDump.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Postal Dump (for Developers): Print all javascript postal messages in the console. 3 | // @version 1.0 4 | // @author PredatH0r 5 | // @description Enable developer console messages with /web_console 1 (restart may be required). 6 | // @description After that you'll see all postal notification about cvar changes and other events. 7 | // @enabled 0 8 | // ==/UserScript== 9 | 10 | /* 11 | 12 | for developers only. 13 | uncomment the last line of this script to get all "postal" notifications logged in your console 14 | 15 | */ 16 | 17 | (function () { 18 | // external global variables 19 | var qz_instance = window.qz_instance; 20 | var console = window.console; 21 | 22 | function init() { 23 | var postal = window.req("postal"); 24 | var channel = postal.channel(); 25 | channel.subscribe("#", onPostalEvent); 26 | echo("^2postalDump.js installed"); 27 | } 28 | 29 | function log(msg) { 30 | console.log(msg); 31 | } 32 | 33 | function echo(msg) { 34 | msg = msg.replace(/\"/g, "'").replace(/[\r\n]+/g, " "); 35 | qz_instance.SendGameCommand("echo \"" + msg + "\""); 36 | } 37 | 38 | function onPostalEvent(data, envelope) { 39 | echo("postal data: " + JSON.stringify(data)); 40 | echo("postal envelope: " + JSON.stringify(envelope)); 41 | } 42 | 43 | // there is a race condition between QL's bundle.js and the userscripts. 44 | // if window.req was published by bundle.js, we're good to go. 45 | // otherwise add a callback to main_hook_v2, which will be called by bundle.js later 46 | if (window.req) 47 | init(); 48 | else { 49 | var oldHook = window.main_hook_v2; 50 | window.main_hook_v2 = function() { 51 | if (typeof oldHook == "function") 52 | oldHook(); 53 | init(); 54 | } 55 | } 56 | })(); 57 | 58 | -------------------------------------------------------------------------------- /source/scripts/steamNick.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name SteamNick: Adds cvars to change your Steam nickname 3 | // @version 1.3 4 | // @author PredatH0r 5 | // @description /steamnick sets your Steam nickname to 6 | // @description /sn_clan + /sn_name + /sn_suffix are combined into /steamnick 7 | // @description Only the sn_* variables will change your Steam Nick when QL is loaded. 8 | // @description You can use /steamnick in combination with AutoExec's gamestart.cfg/gameend.cfg. 9 | // @enabled 1 10 | // ==/UserScript== 11 | 12 | /* 13 | 14 | Version 1.3 15 | - added /sn_suffix 16 | 17 | Version 1.2 18 | - improved error handling when name change fails 19 | 20 | version 1.1 21 | - added /sn_name and /sn_clan 22 | 23 | Version 1.0 24 | - first user script designed to work with Steam exclusive version of Quake Live 25 | 26 | */ 27 | 28 | (function () { 29 | // external global variables 30 | var qz_instance = window.qz_instance; 31 | var console = window.console; 32 | 33 | // constants 34 | var STEAMNICK_CVAR = "steamnick"; 35 | var NAME_CVAR = "sn_name"; 36 | var CLAN_PREFIX_CVAR = "sn_clan"; 37 | var CLAN_SUFFIX_CVAR = "sn_suffix"; 38 | 39 | function init() { 40 | var steamName = qz_instance.GetCvar("name"); 41 | 42 | var clanPrefix = qz_instance.GetCvar(CLAN_PREFIX_CVAR); 43 | if (!clanPrefix) { 44 | // make sure the CVAR exists 45 | qz_instance.SetCvar(CLAN_PREFIX_CVAR, ""); 46 | clanPrefix = ""; 47 | } 48 | 49 | var clanSuffix = qz_instance.GetCvar(CLAN_SUFFIX_CVAR); 50 | if (!clanSuffix) { 51 | // make sure the CVAR exists 52 | qz_instance.SetCvar(CLAN_SUFFIX_CVAR, ""); 53 | clanSuffix = ""; 54 | } 55 | 56 | var nickname = qz_instance.GetCvar(NAME_CVAR); 57 | if (!nickname) { 58 | // make sure the CVAR exists 59 | qz_instance.SetCvar(NAME_CVAR, steamName); 60 | nickname = ""; 61 | } 62 | 63 | 64 | // if any of the name/tag cvars are set, then modify the steam name accordingly 65 | if (clanPrefix || clanSuffix || nickname) { 66 | var steamnick = clanPrefix + nickname + clanSuffix; 67 | qz_instance.SetCvar(STEAMNICK_CVAR, steamnick); 68 | onSteamNickCvarChanged({ name: STEAMNICK_CVAR, value: steamnick }); 69 | } 70 | else 71 | qz_instance.SetCvar(STEAMNICK_CVAR, steamName); 72 | 73 | 74 | var postal = window.req("postal"); 75 | var channel = postal.channel(); 76 | channel.subscribe("cvar." + STEAMNICK_CVAR, onSteamNickCvarChanged); 77 | channel.subscribe("cvar." + CLAN_PREFIX_CVAR, onSteamNickCvarChanged); 78 | channel.subscribe("cvar." + CLAN_SUFFIX_CVAR, onSteamNickCvarChanged); 79 | channel.subscribe("cvar." + NAME_CVAR, onSteamNickCvarChanged); 80 | echo("^2steamNick.js installed"); 81 | } 82 | 83 | function log(msg) { 84 | console.log(msg); 85 | } 86 | 87 | function echo(msg) { 88 | msg = msg.replace(/\"/g, "'").replace(/[\r\n]+/g, " "); 89 | qz_instance.SendGameCommand("echo \"" + msg + "\""); 90 | } 91 | 92 | function onSteamNickCvarChanged(data) { 93 | // German keyboard cannot enter ^ (it's the console key), so allow \ for colors too. Updating the cvar will call this function again. 94 | var value = data.value.replace(/\\/g, "^"); 95 | if (value != data.value) { 96 | qz_instance.SetCvar(data.name, value); 97 | return; 98 | } 99 | 100 | // if any of the sn_* cvars was changed, combine them into "steamnick", which will cause another call to this function 101 | if (data.name != STEAMNICK_CVAR) { 102 | var newNick = qz_instance.GetCvar(CLAN_PREFIX_CVAR) + qz_instance.GetCvar(NAME_CVAR) + qz_instance.GetCvar(CLAN_SUFFIX_CVAR); 103 | if (newNick) 104 | qz_instance.SetCvar(STEAMNICK_CVAR, newNick); 105 | return; 106 | } 107 | 108 | // don't allow empty steam nicknames 109 | if (!value || value == qz_instance.GetCvar("name")) 110 | return; 111 | 112 | // call the extraQL.exe servlet which changes the Steam nickname through a steam_api.dll call 113 | var xhttp = new XMLHttpRequest(); 114 | xhttp.timeout = 10000; 115 | xhttp.onload = function () { 116 | if (xhttp.status == 200) 117 | echo("^3/" + data.name + " changed successfully.^7"); 118 | else { 119 | echo("^1/" + data.name + " failed: ^7" + xhttp.responseText); 120 | qz_instance.SetCvar(STEAMNICK_CVAR, qz_instance.GetCvar("name")); 121 | } 122 | } 123 | xhttp.onerror = function() { 124 | echo("^1/" + STEAMNICK_CVAR + " timed out. ^7Please make sure extraQL.exe 2.0 (or newer) is running on your PC."); 125 | qz_instance.SetCvar(STEAMNICK_CVAR, qz_instance.GetCvar("name")); 126 | } 127 | xhttp.open("GET", "http://localhost:27963/steamnick?name=" + encodeURIComponent(value), true); 128 | xhttp.send(); 129 | } 130 | 131 | 132 | // there is a race condition between QL's bundle.js and the userscripts. 133 | // if window.req was published by bundle.js, we're good to go. 134 | // otherwise add a callback to main_hook_v2, which will be called by bundle.js later 135 | if (window.req) 136 | init(); 137 | else { 138 | var oldHook = window.main_hook_v2; 139 | window.main_hook_v2 = function() { 140 | if (typeof oldHook == "function") 141 | oldHook(); 142 | init(); 143 | } 144 | } 145 | })(); 146 | 147 | -------------------------------------------------------------------------------- /source/steam_api.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/steam_api.dll -------------------------------------------------------------------------------- /source/translation.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/source/translation.xlsx -------------------------------------------------------------------------------- /workshop/extraQL.vdf: -------------------------------------------------------------------------------- 1 | "workshopitem" 2 | { 3 | "appid" "282440" 4 | "publishedfileid" "539252269" 5 | "contentfolder" "d:\sources\extraQL\workshop\content" 6 | "previewfile" "d:\sources\extraQL\workshop\extraQL1.png" 7 | "visibility" "0" 8 | "title" "extraQL userscripts" 9 | "changenote" "https://github.com/PredatH0r/extraQL/releases/tag/v2.24" 10 | } 11 | -------------------------------------------------------------------------------- /workshop/extraQL1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/workshop/extraQL1.png -------------------------------------------------------------------------------- /workshop/extraQL2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/workshop/extraQL2.png -------------------------------------------------------------------------------- /workshop/extraQL3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredatH0r/extraQL/c5e6f951647c3b879e40378b670ae39b416ab904/workshop/extraQL3.png --------------------------------------------------------------------------------