英文:
Creating a C# Script to login to WikiMedia Api
问题
我正在尝试创建一个脚本来登录到一个私人维基站,以便我可以进行更改。以下是我用来实现这个目的的代码。当我发送登录令牌请求时,我成功地获得了一个登录令牌。然而,当我使用该令牌发送登录请求时,我得到了一个"失败"的结果,并附带一个原因,说明"无法继续登录。您的会话很可能已经超时。"
apiUrl被设置为维基百科的URL,但在我的私人维基站上也存在这个问题。
// 代码部分
我应该收到这个:
{
"login": {
"result": "Success",
"lguserid": 1,
"lgusername": "UserName"
}
}
但实际上我收到了这个:
{
"login": {
"result": "Failed",
"reason": "无法继续登录。您的会话很可能已经超时。"
}
}
无论我做什么,我都会得到这条消息。即使我输入了错误的用户名和密码。我尽我所能地去判断,这实际上并不是超时的问题,还有其他的情况发生。
我在这里缺少了什么?
英文:
I am trying to create a script to login to a private Wiki so that I can make changes. This is the code I created to do this. When I send this login token request I successfully get a login token. However when I use that token and send a request to login I get a result of "Failed" with a reason stating "Unable to continue login. Your session most likely timed out."
The apiUrl is set to the Wikipedia url, but the problem exists on my private wiki as well.
public static class WikiAccess
{
static string apiUrl = "https://en.wikipedia.org/w/api.php";
// I am calling this with a working bot username and password
public static async Task<bool> LoginToWikiAsync(string userName, string password)
{
// Create the request parameters to get a login token
var tokenParameters = new Dictionary<string, string>
{
{ "action", "query" },
{ "format", "json" },
{ "meta", "tokens" },
{ "formatversion", "2" },
{ "type", "login" },
};
// Send the login Token Request
var tokenResponse = await SendMessageToWikiGet(tokenParameters);
JObject tokenJsonResponse = ParseString(tokenResponse);
// this gets the login token
string loginToken = tokenJsonResponse["query"]["tokens"]["logintoken"].Value<string>();
var loginParameters = new Dictionary<string, string>
{
{ "action", "login" },
{ "format", "json" },
{ "lgname", userName },
{ "lgpassword", password },
{ "lgtoken", loginToken },
{ "formatversion", "2" },
};
// Send login request
var loginResponse = await SendMessageToWikiPost(loginParameters);
JObject loginJsonResponse = ParseString(loginResponse);
Debug.Log("Login Result: " + loginJsonResponse);
/*
Login Result: {
"login": {
"result": "Failed",
"reason": "Unable to continue login. Your session most likely timed out."
}
}
*/
string loginResult = loginJsonResponse["login"]["result"].Value<string>();
// The login result returns "Failed"
return loginResult == "Success";
}
static JObject ParseString(string value)
{
try
{
return JObject.Parse(value);
}
catch (Exception ex)
{
Debug.LogError(value);
throw ex;
}
}
public static Task<string> SendMessageToWikiGet(Dictionary<string, string> parameters)
{
// Build the query string and send the request to the url via Get
string url = apiUrl + "?" + BuildQueryString(parameters);
Debug.Log(url);
return SendMessageToUrlGet(url);
}
public static Task<string> SendMessageToWikiPost(Dictionary<string, string> parameters)
{
// Build the query string and send the request to the url via Post
return SendMessageToUrlPost(apiUrl, BuildQueryString(parameters));
}
static WebClient client;
public async static Task<string> SendMessageToUrlPost(string url, string parameters)
{
client ??= new();
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
var response = await client.UploadStringTaskAsync(url, parameters);
return response;
}
public async static Task<string> SendMessageToUrlGet(string url)
{
client ??= new();
var tokenResponse = await client.DownloadStringTaskAsync(url);
return tokenResponse;
}
static string BuildQueryString(Dictionary<string, string> parameters)
{
StringBuilder queryBuilder = new StringBuilder();
foreach (var parameter in parameters)
{
queryBuilder.Append($"{Uri.EscapeDataString(parameter.Key)}={Uri.EscapeDataString(parameter.Value)}&");
}
string returnValue = queryBuilder.ToString().TrimEnd('&');
//Debug.Log(returnValue);
return returnValue;
}
}
I should be receiving this:
{
"login": {
"result": "Success",
"lguserid": 1,
"lgusername": "UserName"
}
}
but instead I am receiving this:
{
"login": {
"result": "Failed",
"reason": "Unable to continue login. Your session most likely timed out."
}
}
I will get this message no matter what I do. Even if I put in an incorrect user name and password. As best as I can tell this is not actually timing out and something else is happening.
What am I missing here?
答案1
得分: 1
我找到了解决方法。在某个时候,MediaWiki开始要求使用cookie才能正常工作。所以我将这个部分更改为:
static CookieContainer cookieContainer;
public async static Task<string> PostUsingHttpCLient(string path, Dictionary<string, string> parameters)
{
cookieContainer ??= new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler))
{
var tokenResponse = await client.PostAsync(path, new FormUrlEncodedContent(parameters));
return await tokenResponse.Content.ReadAsStringAsync();
}
}
现在它可以正常工作了!
英文:
I ended up figuring it out. At some point MediaWiki started requiring cookies in order to work properly. So I changed this:
static WebClient client;
public async static Task<string> SendMessageToUrlPost(string url, string parameters)
{
client ??= new();
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
var response = await client.UploadStringTaskAsync(url, parameters);
return response;
}
public async static Task<string> SendMessageToUrlGet(string url)
{
client ??= new();
var tokenResponse = await client.DownloadStringTaskAsync(url);
return tokenResponse;
}
static string BuildQueryString(Dictionary<string, string> parameters)
{
StringBuilder queryBuilder = new StringBuilder();
foreach (var parameter in parameters)
{
queryBuilder.Append($"{Uri.EscapeDataString(parameter.Key)}={Uri.EscapeDataString(parameter.Value)}&");
}
string returnValue = queryBuilder.ToString().TrimEnd('&');
//Debug.Log(returnValue);
return returnValue;
}
To this:
static CookieContainer cookieContainer;
public async static Task<string> PostUsingHttpCLient(string path, Dictionary<string, string> parameters)
{
cookieContainer ??= new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler))
{
var tokenResponse = await client.PostAsync(path, new FormUrlEncodedContent(parameters));
return await tokenResponse.Content.ReadAsStringAsync();
}
}
Now it works just fine!
Edit:
Here is the full script with some basic functions and the infrastructure to expand on this if anyone is interested.
using Newtonsoft.Json.Linq;
using Sirenix.OdinInspector;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
public class WikiAccess : MonoBehaviour
{
static string apiUrl = https://www.Your_Wiki_URL.com/w/api.php";
[ShowInInspector] static bool isLoggedIn;
[ShowInInspector] static string csrftoken;
[Button]
public static async Task CheckCredentials()
{
#if UNITY_EDITOR
string userName = "bot_UserName";
string password = "bot_password";
if (!isLoggedIn) await LoginToWikiAsync();
if (string.IsNullOrEmpty(csrftoken)) await Get_CSRF_Token();
async Task LoginToWikiAsync()
{
// Create the token parameters to get a login token
var tokenParameters = new Dictionary<string, string>
{
{ "action", "query" },
{ "format", "json" },
{ "meta", "tokens" },
{ "type", "login" },
};
// Send the login Token Request
var tokenResponse = await PostMesssageToURL(tokenParameters);
// Get the login token
string loginToken = tokenResponse["query"]["tokens"]["logintoken"].Value<string>();
var loginParameters = new Dictionary<string, string>
{
{ "action", "login" },
{ "format", "json" },
{ "lgname", userName },
{ "lgpassword", password },
{ "lgtoken", loginToken },
};
// Send login request
isLoggedIn = (await PostMesssageToURL(loginParameters))["login"]["result"].Value<string>() == "Success";
}
async Task Get_CSRF_Token()
{
var tokenParameters = new Dictionary<string, string>
{
{ "action", "query" },
{ "format", "json" },
{ "meta", "tokens" },
{ "formatversion", "2" },
};
csrftoken = (await PostMesssageToURL(tokenParameters))["query"]["tokens"]["csrftoken"].Value<string>();
}
#endif
}
public static async Task EditPage(string pageTitle, string content)
{
await CheckCredentials();
var parameters = new Dictionary<string, string>
{
{ "action", "edit" },
{ "format", "json" },
{ "title", pageTitle },
{ "text", content },
{ "token", csrftoken }
};
var returnValue = await PostMesssageToURL(parameters);
Debug.Log(returnValue);
}
/// <summary>
/// Uploads a Unity Asset
/// </summary>
public static Task UploadFile(UnityEngine.Object obj)
{
#if UNITY_EDITOR
return UploadFile(AssetDatabase.GetAssetPath(obj).Replace("Assets/", Application.dataPath + "/"), obj.name);
#endif
}
/// <summary>
/// Upload a File
/// </summary>
public static async Task UploadFile(string path, string fileName)
{
if (!File.Exists(path)) throw new FileNotFoundException();
await CheckCredentials();
var parameters = new Dictionary<string, string>
{
{ "action", "upload" },
{ "format", "json" },
{ "filename", fileName },
{ "ignorewarnings", "1" },
{ "token", csrftoken }
};
// Read file content
var bytes = await File.ReadAllBytesAsync(path);
// Create Byte Array content
var fileContent = new ByteArrayContent(bytes);
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "file",
FileName = fileName
};
// send the File
Debug.Log("Uploading File...");
var returnValue = await PostMesssageToURL(apiUrl, parameters, new List<HttpContent>()
{
fileContent
});
Debug.Log("File uploaded.");
Debug.Log(returnValue);
}
[ShowInInspector] static CookieContainer cookieContainer;
/// <summary>
/// Send a simple message and recieve a JObject Back
/// </summary>
public static Task<JObject> PostMesssageToURL(Dictionary<string, string> parameters) => PostMesssageToURL(apiUrl, parameters, new());
/// <summary>
/// Sends a post message to the url with json prameters and any additional Http Content
/// </summary>
/// <param name="url">apiUrl</param>
/// <param name="parameters">POST Parameters</param>
/// <param name="additionalContent">Use this to add HttpContent to the MultipartFormDataContent</param>
/// <returns></returns>
public async static Task<JObject> PostMesssageToURL(string url, Dictionary<string, string> parameters, List<HttpContent> additionalContent)
{
// Stores cookies needed to login
cookieContainer ??= new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler))
{
var multiPartContent = new MultipartFormDataContent();
// Add Parameters
foreach (var kvp in parameters) multiPartContent.Add(new StringContent(kvp.Value), kvp.Key);
// Add Additional http content
foreach (var content in additionalContent) multiPartContent.Add(content);
// Post Message
var httpResponse = await client.PostAsync(url, multiPartContent);
string response = await httpResponse.Content.ReadAsStringAsync();
multiPartContent.Dispose();
// return JObject
try
{
return JObject.Parse(response);
}
catch (Exception ex)
{
Debug.LogError(response);
throw ex;
}
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论