mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-01-22 09:01:57 -05:00
Use Classicube's api rather than web scraping.
This commit is contained in:
parent
f69596db27
commit
887d846ef2
10 changed files with 138 additions and 128 deletions
|
@ -109,7 +109,8 @@ namespace Launcher2 {
|
|||
}
|
||||
|
||||
void ConnectToServer( int mouseX, int mouseY ) {
|
||||
game.ConnectToServer( Get( 3 ) );
|
||||
LauncherTableWidget table = (LauncherTableWidget)widgets[tableIndex];
|
||||
game.ConnectToServer( table.servers, Get( 3 ) );
|
||||
}
|
||||
|
||||
void MouseWheelChanged( object sender, MouseWheelEventArgs e ) {
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace Launcher2 {
|
|||
TableEntry entry = usedEntries[i];
|
||||
if( mouseY >= entry.Y && mouseY < entry.Y + entry.Height ) {
|
||||
if( lastIndex == i && (DateTime.UtcNow - lastPress).TotalSeconds > 1 ) {
|
||||
Window.ConnectToServer( entry.Hash );
|
||||
Window.ConnectToServer( servers, entry.Hash );
|
||||
lastPress = DateTime.UtcNow;
|
||||
}
|
||||
SelectedChanged( entry.Hash );
|
||||
|
|
|
@ -15,9 +15,11 @@ namespace Launcher2 {
|
|||
public Action<string> SelectedChanged;
|
||||
|
||||
TableEntry[] entries, usedEntries;
|
||||
internal List<ServerListEntry> servers;
|
||||
public void SetEntries( List<ServerListEntry> servers ) {
|
||||
entries = new TableEntry[servers.Count];
|
||||
usedEntries = new TableEntry[servers.Count];
|
||||
this.servers = servers;
|
||||
int index = 0;
|
||||
|
||||
foreach( ServerListEntry e in servers ) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<ProjectGuid>{3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5}</ProjectGuid>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Launcher2</RootNamespace>
|
||||
<AssemblyName>Launcher2</AssemblyName>
|
||||
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
|
||||
|
|
|
@ -82,8 +82,20 @@ namespace Launcher2 {
|
|||
screen.Init();
|
||||
}
|
||||
|
||||
public bool ConnectToServer( string hash ) {
|
||||
public bool ConnectToServer( List<ServerListEntry> publicServers, string hash ) {
|
||||
if( String.IsNullOrEmpty( hash ) ) return false;
|
||||
|
||||
ClientStartData data = null;
|
||||
foreach( ServerListEntry entry in publicServers ) {
|
||||
if( entry.Hash == hash ) {
|
||||
data = new ClientStartData( Session.Username, entry.Mppass,
|
||||
entry.IPAddress, entry.Port );
|
||||
Client.Start( data, true );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to private server handling
|
||||
try {
|
||||
data = Session.GetConnectInfo( hash );
|
||||
} catch( WebException ex ) {
|
||||
|
|
|
@ -17,20 +17,21 @@
|
|||
// <website>https://github.com/facebook-csharp-sdk/simple-json</website>
|
||||
//-----------------------------------------------------------------------
|
||||
// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
|
||||
// This is a significantly cutdown version of the original simple-json repository.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace SimpleJson {
|
||||
namespace Launcher2 {
|
||||
|
||||
public static class SimpleJson {
|
||||
public static class Json {
|
||||
const int TOKEN_NONE = 0, TOKEN_CURLY_OPEN = 1, TOKEN_CURLY_CLOSE = 2;
|
||||
const int TOKEN_SQUARED_OPEN = 3, TOKEN_SQUARED_CLOSE = 4, TOKEN_COLON = 5;
|
||||
const int TOKEN_COMMA = 6, TOKEN_STRING = 7, TOKEN_NUMBER = 8;
|
||||
const int TOKEN_TRUE = 9, TOKEN_FALSE = 10, TOKEN_NULL = 11;
|
||||
|
||||
static Dictionary<string, object> ParseObject( char[] json, ref int index, ref bool success ) {
|
||||
static Dictionary<string, object> ParseObject( string json, ref int index, ref bool success ) {
|
||||
Dictionary<string, object> table = new Dictionary<string, object>();
|
||||
NextToken( json, ref index ); // skip {
|
||||
|
||||
|
@ -61,7 +62,7 @@ namespace SimpleJson {
|
|||
}
|
||||
}
|
||||
|
||||
static List<object> ParseArray( char[] json, ref int index, ref bool success ) {
|
||||
static List<object> ParseArray( string json, ref int index, ref bool success ) {
|
||||
List<object> array = new List<object>();
|
||||
NextToken( json, ref index ); // [
|
||||
|
||||
|
@ -82,7 +83,7 @@ namespace SimpleJson {
|
|||
}
|
||||
}
|
||||
|
||||
static object ParseValue( char[] json, ref int index, ref bool success ) {
|
||||
public static object ParseValue( string json, ref int index, ref bool success ) {
|
||||
switch ( LookAhead( json, index ) ) {
|
||||
case TOKEN_STRING:
|
||||
return ParseString( json, ref index, ref success );
|
||||
|
@ -107,7 +108,7 @@ namespace SimpleJson {
|
|||
success = false; return null;
|
||||
}
|
||||
|
||||
static string ParseString( char[] json, ref int index, ref bool success ) {
|
||||
static string ParseString( string json, ref int index, ref bool success ) {
|
||||
StringBuilder s = new StringBuilder( 400 );
|
||||
EatWhitespace( json, ref index );
|
||||
char c = json[index++]; // "
|
||||
|
@ -129,7 +130,8 @@ namespace SimpleJson {
|
|||
if( remainingLength >= 4 ) {
|
||||
// parse the 32 bit hex into an integer codepoint
|
||||
uint codePoint;
|
||||
if( !( success = UInt32.TryParse( new String( json, index, 4 ), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint ) ) )
|
||||
string str = json.Substring( index, 4 );
|
||||
if( !( success = UInt32.TryParse( str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint ) ) )
|
||||
return "";
|
||||
|
||||
s.Append( new String( (char)codePoint, 1 ) );
|
||||
|
@ -148,46 +150,37 @@ namespace SimpleJson {
|
|||
}
|
||||
|
||||
const StringComparison caseless = StringComparison.OrdinalIgnoreCase;
|
||||
static object ParseNumber( char[] json, ref int index, ref bool success ) {
|
||||
static object ParseNumber( string json, ref int index, ref bool success ) {
|
||||
EatWhitespace( json, ref index );
|
||||
int lastIndex = GetLastIndexOfNumber( json, index );
|
||||
int charLength = (lastIndex - index) + 1;
|
||||
string str = new String( json, index, charLength );
|
||||
string str = json.Substring( index, charLength );
|
||||
index = lastIndex + 1;
|
||||
|
||||
if( str.IndexOf( ".", caseless ) != -1 || str.IndexOf( "e", caseless ) != -1 ) {
|
||||
double number;
|
||||
success = double.TryParse( str, NumberStyles.Any, CultureInfo.InvariantCulture, out number );
|
||||
return number;
|
||||
} else {
|
||||
long number;
|
||||
success = long.TryParse( str, NumberStyles.Any, CultureInfo.InvariantCulture, out number );
|
||||
return number;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static int GetLastIndexOfNumber( char[] json, int index ) {
|
||||
static int GetLastIndexOfNumber( string json, int index ) {
|
||||
int lastIndex = index;
|
||||
for( ; lastIndex < json.Length; lastIndex++ ) {
|
||||
if( "0123456789+-.eE".IndexOf( json[lastIndex] ) == -1 )
|
||||
if( "0123456789+-".IndexOf( json[lastIndex] ) == -1 )
|
||||
break;
|
||||
}
|
||||
return lastIndex - 1;
|
||||
}
|
||||
|
||||
static void EatWhitespace( char[] json, ref int index ) {
|
||||
static void EatWhitespace( string json, ref int index ) {
|
||||
for( ; index < json.Length; index++ ) {
|
||||
if( " \t\n\r\b\f".IndexOf( json[index] ) == -1 )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int LookAhead( char[] json, int index ) {
|
||||
static int LookAhead( string json, int index ) {
|
||||
int saveIndex = index;
|
||||
return NextToken( json, ref saveIndex );
|
||||
}
|
||||
|
||||
static int NextToken( char[] json, ref int index ) {
|
||||
static int NextToken( string json, ref int index ) {
|
||||
EatWhitespace( json, ref index );
|
||||
if( index == json.Length )
|
||||
return TOKEN_NONE;
|
||||
|
@ -215,7 +208,7 @@ namespace SimpleJson {
|
|||
return TOKEN_NONE;
|
||||
}
|
||||
|
||||
static bool CompareConstant( char[] json, ref int index, string value ) {
|
||||
static bool CompareConstant( string json, ref int index, string value ) {
|
||||
int remaining = json.Length - index;
|
||||
if( remaining < value.Length ) return false;
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
namespace Launcher2 {
|
||||
|
||||
public sealed class ClassicubeSession : IWebTask {
|
||||
|
||||
const string classicubeNetUri = "https://www.classicube.net/",
|
||||
loginUri = "https://www.classicube.net/acc/login",
|
||||
publicServersUri = "https://www.classicube.net/server/list",
|
||||
playUri = "https://www.classicube.net/server/play/";
|
||||
loginUri = "https://www.classicube.net/api/login/",
|
||||
publicServersUri = "https://www.classicube.net/api/servers",
|
||||
playUri = "https://www.classicube.net/api/server/";
|
||||
const string wrongCredentialsMessage = "Login failed";
|
||||
const string loggedInAs = @"<a href=""/acc"" class=""button"">";
|
||||
StringComparison ordinal = StringComparison.Ordinal;
|
||||
|
@ -55,109 +55,65 @@ namespace Launcher2 {
|
|||
Username = user;
|
||||
// Step 1: GET csrf token from login page.
|
||||
var swGet = System.Diagnostics.Stopwatch.StartNew();
|
||||
var getResponse = GetHtml( loginUri, classicubeNetUri );
|
||||
string token = null;
|
||||
foreach( string line in getResponse ) {
|
||||
//Console.WriteLine( line );
|
||||
if( line.StartsWith( @" <input id=""csrf_token""", ordinal ) ) {
|
||||
const int tokenStart = 68;
|
||||
int tokenEnd = line.IndexOf( '"', tokenStart );
|
||||
token = line.Substring( tokenStart, tokenEnd - tokenStart );
|
||||
Log( "cc token get took " + swGet.ElapsedMilliseconds );
|
||||
swGet.Stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
string getResponse = GetHtmlAll( loginUri, classicubeNetUri );
|
||||
int index = 0; bool success = true;
|
||||
Dictionary<string, object> data =
|
||||
(Dictionary<string, object>)Json.ParseValue( getResponse, ref index, ref success );
|
||||
string token = (string)data["token"];
|
||||
|
||||
// Step 2: POST to login page with csrf token.
|
||||
string loginData = String.Format(
|
||||
"csrf_token={0}&username={1}&password={2}",
|
||||
Uri.EscapeDataString( token ),
|
||||
"username={0}&password={1}&token={2}",
|
||||
Uri.EscapeDataString( user ),
|
||||
Uri.EscapeDataString( password )
|
||||
Uri.EscapeDataString( password ),
|
||||
Uri.EscapeDataString( token )
|
||||
);
|
||||
Log( "cc login took " + swGet.ElapsedMilliseconds );
|
||||
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
var response = PostHtml( loginUri, loginUri, loginData );
|
||||
foreach( string line in response ) {
|
||||
if( line.Contains( wrongCredentialsMessage ) ) {
|
||||
throw new InvalidOperationException( "Wrong username or password." );
|
||||
} else if( line.Contains( loggedInAs ) ) {
|
||||
Log( "cc login took " + sw.ElapsedMilliseconds );
|
||||
sw.Stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
string response = PostHtmlAll( loginUri, loginUri, loginData );
|
||||
index = 0; success = true;
|
||||
data = (Dictionary<string, object>)Json.ParseValue( response, ref index, ref success );
|
||||
|
||||
List<object> errors = (List<object>)data["errors"];
|
||||
if( errors.Count > 0 )
|
||||
throw new InvalidOperationException( "Wrong username or password." );
|
||||
|
||||
Username = (string)data["username"];
|
||||
Log( "cc login took " + sw.ElapsedMilliseconds );
|
||||
sw.Stop();
|
||||
}
|
||||
|
||||
public ClientStartData GetConnectInfo( string hash ) {
|
||||
string uri = playUri + hash;
|
||||
var response = GetHtml( uri, classicubeNetUri );
|
||||
ClientStartData data = new ClientStartData();
|
||||
data.Username = Username;
|
||||
string response = GetHtmlAll( uri, classicubeNetUri );
|
||||
|
||||
foreach( string line in response ) {
|
||||
int index = 0;
|
||||
// Look for <param name="x" value="x"> tags
|
||||
if( (index = line.IndexOf( "<param", ordinal )) > 0 ) {
|
||||
int nameStart = index + 13;
|
||||
int nameEnd = line.IndexOf( '"', nameStart );
|
||||
string paramName = line.Substring( nameStart, nameEnd - nameStart );
|
||||
|
||||
// Don't read param value by default so we avoid allocating unnecessary 'value' strings.
|
||||
if( paramName == "server" ) {
|
||||
data.Ip = GetParamValue( line, nameEnd );
|
||||
} else if( paramName == "port" ) {
|
||||
data.Port = GetParamValue( line, nameEnd );
|
||||
} else if( paramName == "mppass" ) {
|
||||
data.Mppass = GetParamValue( line, nameEnd );
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static string GetParamValue( string line, int nameEnd ) {
|
||||
int valueStart = nameEnd + 9;
|
||||
int valueEnd = line.IndexOf( '"', valueStart );
|
||||
return line.Substring( valueStart, valueEnd - valueStart );
|
||||
int index = 0; bool success = true;
|
||||
Dictionary<string, object> root =
|
||||
(Dictionary<string, object>)Json.ParseValue( response, ref index, ref success );
|
||||
List<object> list = (List<object>)root["servers"];
|
||||
|
||||
Dictionary<string, object> pairs = (Dictionary<string, object>)list[0];
|
||||
return new ClientStartData( Username, (string)pairs["mppass"],
|
||||
(string)pairs["ip"], (string)pairs["port"] );
|
||||
}
|
||||
|
||||
public List<ServerListEntry> GetPublicServers() {
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
var response = GetHtml( publicServersUri, classicubeNetUri );
|
||||
List<ServerListEntry> servers = new List<ServerListEntry>();
|
||||
int index = -1;
|
||||
string response = GetHtmlAll( publicServersUri, classicubeNetUri );
|
||||
int index = 0; bool success = true;
|
||||
Dictionary<string, object> root =
|
||||
(Dictionary<string, object>)Json.ParseValue( response, ref index, ref success );
|
||||
List<object> list = (List<object>)root["servers"];
|
||||
|
||||
string hash = null;
|
||||
string name = null;
|
||||
string players = null;
|
||||
string maxPlayers = null;
|
||||
|
||||
foreach( string line in response ) {
|
||||
if( line.StartsWith( " <strong><a href", ordinal ) ) {
|
||||
const int hashStart = 34;
|
||||
int hashEnd = line.IndexOf( '/', hashStart );
|
||||
hash = line.Substring( hashStart, hashEnd - hashStart );
|
||||
|
||||
int nameStart = hashEnd + 3; // point to first char of name
|
||||
int nameEnd = line.IndexOf( '<', nameStart );
|
||||
name = line.Substring( nameStart, nameEnd - nameStart );
|
||||
name = WebUtility.HtmlDecode( name );
|
||||
index++;
|
||||
}
|
||||
if( index < 0 ) continue;
|
||||
|
||||
if( line.StartsWith( @" <td class=""players"">", ordinal ) ) {
|
||||
const int playersStart = 24;
|
||||
int playersEnd = line.IndexOf( '/', playersStart );
|
||||
players = line.Substring( playersStart, playersEnd - playersStart );
|
||||
|
||||
int maxPlayersStart = playersEnd + 1;
|
||||
int maxPlayersEnd = line.IndexOf( ']', playersStart );
|
||||
maxPlayers = line.Substring( maxPlayersStart, maxPlayersEnd - maxPlayersStart );
|
||||
servers.Add( new ServerListEntry( hash, name, players, maxPlayers, "" ) );
|
||||
}
|
||||
foreach( object server in list ) {
|
||||
Dictionary<string, object> pairs = (Dictionary<string, object>)server;
|
||||
servers.Add( new ServerListEntry(
|
||||
(string)pairs["hash"], (string)pairs["name"],
|
||||
(string)pairs["players"], (string)pairs["maxplayers"],
|
||||
(string)pairs["uptime"], (string)pairs["mppass"],
|
||||
(string)pairs["ip"], (string)pairs["port"] ) );
|
||||
}
|
||||
Log( "cc servers took " + sw.ElapsedMilliseconds );
|
||||
sw.Stop();
|
||||
|
|
|
@ -73,11 +73,12 @@ namespace Launcher2 {
|
|||
|
||||
protected string GetHtmlAll( string uri, string referer ) {
|
||||
HttpWebResponse response = MakeRequest( uri, referer, null );
|
||||
using( Stream stream = response.GetResponseStream() ) {
|
||||
using( StreamReader reader = new StreamReader( stream ) ) {
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
return GetResponseAll( response );
|
||||
}
|
||||
|
||||
protected string PostHtmlAll( string uri, string referer, string data ) {
|
||||
HttpWebResponse response = MakeRequest( uri, referer, data );
|
||||
return GetResponseAll( response );
|
||||
}
|
||||
|
||||
protected IEnumerable<string> GetResponseLines( HttpWebResponse response ) {
|
||||
|
@ -91,8 +92,16 @@ namespace Launcher2 {
|
|||
}
|
||||
}
|
||||
|
||||
protected string GetResponseAll( HttpWebResponse response ) {
|
||||
using( Stream stream = response.GetResponseStream() ) {
|
||||
using( StreamReader reader = new StreamReader( stream ) ) {
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void Log( string text ) {
|
||||
System.Diagnostics.Debug.WriteLine( text );
|
||||
Console.WriteLine( text );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,25 @@ namespace Launcher2 {
|
|||
/// <summary> How long the server has been 'alive'. </summary>
|
||||
public string Uptime;
|
||||
|
||||
public ServerListEntry( string hash, string name, string players, string maxPlayers, string uptime ) {
|
||||
/// <summary> IP address the server is hosted on. </summary>
|
||||
public string IPAddress;
|
||||
|
||||
/// <summary> Port the server is hosted on. </summary>
|
||||
public string Port;
|
||||
|
||||
/// <summary> Mppass specific for the user and this server. </summary>
|
||||
public string Mppass;
|
||||
|
||||
public ServerListEntry( string hash, string name, string players, string maxPlayers,
|
||||
string uptime, string mppass, string ip, string port ) {
|
||||
Hash = hash;
|
||||
Name = name;
|
||||
Players = players;
|
||||
MaximumPlayers = maxPlayers;
|
||||
Uptime = uptime;
|
||||
Mppass = mppass;
|
||||
IPAddress = ip;
|
||||
Port = port;
|
||||
}
|
||||
}
|
||||
}
|
24
license.txt
24
license.txt
|
@ -118,6 +118,30 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
Simple-json license
|
||||
======================
|
||||
Copyright (c) 2011, The Outercurve Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
Ionic.Zlib license
|
||||
==================
|
||||
Microsoft Public License (Ms-PL)
|
||||
|
|
Loading…
Reference in a new issue