Под свои нужды понадобилось мне редактировать странички в Mediawiki из кода стороннего приложения.
Сначала я подумал пройтись по дорожке "Mediawiki -> MySQL -> C# -> Unity".
Минус такого подхода минимум в том, что данные в бд хранятся по разному, да и чтобы поставить MySQL под юньку - нужно импортировать библиотеки, а они вставали довольно криво туда.
Сначала я подумал пройтись по дорожке "Mediawiki -> MySQL -> C# -> Unity".
Минус такого подхода минимум в том, что данные в бд хранятся по разному, да и чтобы поставить MySQL под юньку - нужно импортировать библиотеки, а они вставали довольно криво туда.
Но потом понял, что эт всё не нужно - ведь есть чудесный api.php, прилагаемый к Mediawiki.
Искал хорошие решения - они были, но... не под второй дот.нет.
В результате пришлось написать простенький класс самому.
Искал хорошие решения - они были, но... не под второй дот.нет.
В результате пришлось написать простенький класс самому.
/*
* Hello from http://xgm.guru
* Author: Extravert
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
namespace MediaWikiAPI
{
public class MediaWiki
{
public string addressWiki;
public string addressAPI;
public CookieContainer cookies;
public MediaWiki(string address)
{
_Init(address);
}
public MediaWiki(string address, string user, string password)
{
_Init(address);
Login(user, password);
}
private void _Init(string address)
{
addressWiki = address;
_RepairAddress();
cookies = new CookieContainer();
}
private void _RepairAddress()
{
if (string.IsNullOrEmpty(addressWiki))
throw new Exception("Address is empty");
if (!addressWiki.StartsWith("http://"))
addressWiki = "http://" + addressWiki;
if (!addressWiki.EndsWith("/"))
addressWiki = addressWiki + "/";
addressAPI = addressWiki + "api.php?";
}
//Commands
public void Login(string lgname, string lgpassword, string lgdomain = null)
{
log("LOGIN({0}, {1}, {2})", lgname, lgpassword, lgdomain);
var command = CreateCommand(parameter_action.login);
command.AddField("lgname", lgname);
command.AddField("lgpassword", lgpassword);
command.AddField("lgdomain", lgdomain);
if (command.Go() == "Success")
return;
command.AddField("lgtoken", command.GetAnswerValue("token"));
var result = command.Go();
if (result != "Success")
throw new Exception("Не получилось залогиниться. Result = " + result);
}
public void Logout()
{
log("LOGOUT()");
CreateCommand(parameter_action.logout).Go();
}
public void Edit(string title, string text)
{
log("EDIT({0}, {1})", title, text);
var query = CreateCommand(parameter_action.query);
query.AddField("prop", "info|revisions");
query.AddField("intoken", "edit");
query.AddField("titles", title);
query.Go();
var token = query["edittoken"];
var command = CreateCommand(parameter_action.edit);
command.AddField("title", title);
command.AddField("text", text);
command.AddField("token", token);
var result = command.Go();
if (result != "Success")
throw new Exception(result);
}
//Простая оболочка для отправки комманд
private Command CreateCommand()
{
return new Command(this);
}
private Command CreateCommand(parameter_action action)
{
var command = CreateCommand();
command.AddField(action);
return command;
}
private Command CreateCommand(parameter_action action, string formatStringPostData, params object[] formatObjs)
{
var command = CreateCommand();
command.AddField(action);
command.AddField(formatStringPostData, formatObjs);
return command;
}
private Command CreateCommand(string formatStringPostData, params object[] formatObjs)
{
var command = CreateCommand();
command.AddField(formatStringPostData, formatObjs);
return command;
}
private Command CreateCommand(string postData)
{
var command = CreateCommand();
command.AddField(postData);
return command;
}
private class Command
{
private MediaWiki mediaWiki;
private List<string> postData = new List<string>();
public string resultXml;
public Command(MediaWiki mediaWiki, bool asXml = true)
{
this.mediaWiki = mediaWiki;
if (asXml)
AddField(parameter_format.xml);
}
public string this[string keyNew]
{
get { return GetAnswerValue(keyNew); }
set { AddField(keyNew, value); }
}
//Добавляем новые данные
public void AddField(string key, string value)
{
if (value == null)
return;
postData.Add(key + "=" + Uri.EscapeDataString(value));
}
public void AddField(parameter_action action)
{
postData.Add("action=" + action);
}
public void AddField(parameter_format format)
{
postData.Add("format=" + format);
}
public void AddField(parameter_assert assert)
{
postData.Add("assert=" + assert);
}
public void AddField(string postDataString)
{
postData.Add(postDataString);
}
public void AddField(string postDataString, params object[] formatObjs)
{
postData.Add(string.Format(postDataString, formatObjs));
}
public override string ToString()
{
return string.Join("&", postData.ToArray());
}
public string GetAnswerValue(string key)
{
if (resultXml == null)
return null;
var index = resultXml.IndexOf(key + "=" + "\"", StringComparison.Ordinal);
if (index == -1)
return null;
index += key.Length + 2;
var indexSpace = resultXml.IndexOf("\"", index, StringComparison.Ordinal);
if (index == -1)
return null;
return resultXml.Substring(index, indexSpace - index);
}
public string Go(bool getCookies = true, bool allowRedirect = false)
{
resultXml = mediaWiki.PostData(ToString(), getCookies, allowRedirect);
mediaWiki.log(resultXml);
return this["result"];
}
}
public enum parameter_action
{
login, logout, createaccount, query, expandtemplates, parse, opensearch, feedcontributions,
feedrecentchanges, feedwatchlist, help, paraminfo, rsd, compare, tokens, purge,
setnotificationtimestamp, rollback, delete, undelete, protect, block, unblock, move, edit, upload,
filerevert, emailuser, watch, patrol, import, userrights, options, imagerotate, revisiondelete
}
public enum parameter_format
{
json, jsonfm, php, phpfm, wddx, wddxfm, xml, xmlfm, yaml, yamlfm, rawfm, txt, txtfm, dbg, dbgfm,
dump, dumpfm, none
}
public enum parameter_assert
{
user, bot
}
public string PostData(string postData, bool getCookies,
bool allowRedirect)
{
HttpWebResponse webResp = null;
for (int errorCounter = 0;; errorCounter++)
{
var webReq = (HttpWebRequest)WebRequest.Create(addressAPI);
webReq.Proxy.Credentials = CredentialCache.DefaultCredentials;
webReq.UseDefaultCredentials = true;
webReq.ContentType = "application/x-www-form-urlencoded";
webReq.Headers.Add("Cache-Control", "no-cache, must-revalidate");
webReq.UserAgent = "ExBot";
webReq.AllowAutoRedirect = allowRedirect;
webReq.CookieContainer = cookies.Count == 0 ? new CookieContainer() : cookies;
webReq.Headers.Add(HttpRequestHeader.AcceptEncoding, "deflate");
if (!string.IsNullOrEmpty(postData))
{
// if (Bot.isRunningOnMono) // Monobug 636219 evasion
// webReq.AllowAutoRedirect = false;
webReq.Method = "POST";
webReq.Timeout = 180000;
byte[] postBytes = Encoding.UTF8.GetBytes(postData);
webReq.ContentLength = postBytes.Length;
Stream reqStrm = webReq.GetRequestStream();
reqStrm.Write(postBytes, 0, postBytes.Length);
reqStrm.Close();
}
try
{
webResp = (HttpWebResponse)webReq.GetResponse();
break;
}
catch (WebException e)
{
string message = e.Message;
if (webResp == null)
throw;
if (webReq.AllowAutoRedirect == false && webResp.StatusCode == HttpStatusCode.Redirect) // Monobug 636219 evasion
return "";
if (Regex.IsMatch(message, ": \\(50[02349]\\) "))
{ // Remote problem
if (errorCounter > 3)
throw;
// Debug.LogError(message + " " + "Retrying in 60 seconds.");
Thread.Sleep(60000);
}
else if (message.Contains("Section=ResponseStatusLine"))
return PostData(postData, getCookies, allowRedirect);
else throw;
}
}
var respStream = webResp.GetResponseStream();
if (webResp.ContentEncoding.ToLower().Contains("deflate"))
respStream = new DeflateStream(respStream, CompressionMode.Decompress);
if (getCookies)
{
var siteUri = new Uri(addressWiki);
foreach (Cookie cookie in webResp.Cookies)
{
if (cookie.Domain[0] == '.' &&
cookie.Domain.Substring(1) == siteUri.Host)
cookie.Domain = cookie.Domain.TrimStart(new[] { '.' });
cookies.Add(cookie);
}
}
if (respStream != null)
{
var strmReader = new StreamReader(respStream, Encoding.UTF8);
string respStr = strmReader.ReadToEnd();
strmReader.Close();
webResp.Close();
return respStr;
}
return null;
}
//ДЛЯ ОТЛАДКИ
public Action<string> debug { private get; set; }
private void log(string str)
{
if (debug != null)
debug(str);
}
private void log(string str, params object[] objs)
{
if (debug != null)
debug(string.Format(str, objs));
}
}
}
Пример применения:
var mediawiki = new MediaWiki("http://xgm.guru/wiki/");
mediawiki.debug = s => Debug.Log(s); //Если не нужен лог для отладки - можно не писать
mediawiki.Login("admin", "qwerty123");
mediawiki.Edit("TestPage", "TestText");
mediawiki.Logout();
Если разобраться в функциях Login/Edit/Logout в принципе можно дополнить класс другими командами. Вся справка по api отображается когда открываешь api.php в браузере.
Тобиш мысль такая - пишу код, сохраняю, в инетике автоматически складывается документация по написанной библиотеке. Удобно же.
Пока отложил эту задачку в сторону, но мб еще осознаю как это сделать без гемора.