„Benutzer:Mps/AnimeListenUpdater.cs“ – Versionsunterschied
Erscheinungsbild
Inhalt gelöscht Inhalt hinzugefügt
Mps (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Mps (Diskussion | Beiträge) KKeine Bearbeitungszusammenfassung |
||
Zeile 2: | Zeile 2: | ||
using System; |
using System; |
||
using System.Collections.Generic; |
using System.Collections.Generic; |
||
using System.Diagnostics; |
|||
using System.Globalization; |
using System.Globalization; |
||
using System.IO; |
using System.IO; |
||
Zeile 14: | Zeile 15: | ||
namespace AnimeListenUpdater |
namespace AnimeListenUpdater |
||
{ |
{ |
||
#region Exceptions |
|||
class AnimeListenUpdaterException : Exception |
|||
class BusinessException : Exception |
|||
{ |
|||
public BusinessException(string message) |
|||
: base(message) |
|||
{ } |
|||
} |
|||
class MediawikiException : BusinessException |
|||
{ |
|||
public string Code { get; } |
|||
public MediawikiException(string message) |
|||
: base(message) |
|||
{ } |
|||
public MediawikiException(string code, string info) |
|||
: base("Mediawiki error \"" + code + ": " + info + "\"") |
|||
{ |
{ |
||
Code = code; |
|||
public AnimeListenUpdaterException(String message) |
|||
: base(message) |
|||
{ } |
|||
} |
} |
||
} |
|||
class MediawikiException : AnimeListenUpdaterException |
|||
class MediawikiLagException : MediawikiException |
|||
{ |
|||
public int LagTime { get; private set; } |
|||
public MediawikiLagException(string code, string info) |
|||
: base("Mediawiki error \"" + code + ": " + info + "\"") |
|||
{ |
{ |
||
LagTime = GetLagTime(info); |
|||
public MediawikiException(String message) |
|||
} |
|||
: base(message) |
|||
{ } |
|||
public static int GetLagTime(string info) |
|||
public MediawikiException(String code, String info) |
|||
{ |
|||
: base("Mediawiki error \"" + code + ": " + info + "\"") |
|||
int lagtime = -1; |
|||
Match match = Regex.Match(info, "Waiting for [^ ]*: ([0-9.-]+) seconds lagged"); |
|||
if (match.Success) int.TryParse(match.Groups[1].Value, out lagtime); |
|||
return lagtime; |
|||
} |
} |
||
} |
|||
#endregion |
|||
public class Mediawiki |
|||
{ |
|||
#region Netzwerk- und Mediawiki-Funktionen |
|||
static CookieContainer cookieContainer = new CookieContainer(); |
|||
static readonly string userAgentString = GetUserAgentString(); |
|||
static string GetUserAgentString() |
|||
{ |
{ |
||
AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName(); |
|||
protected String language; |
|||
static CookieContainer cookieContainer = new CookieContainer(); |
|||
static readonly String userAgentString = GetUserAgentString(); |
|||
protected bool loggedIn = false; |
|||
// Programmname und -version |
|||
public Mediawiki(String lang) |
|||
string userAgent = assemblyName.Name + "/" + assemblyName.Version.Major + "." + assemblyName.Version.Minor; |
|||
{ |
|||
// Umgebungsinformationen (OS, Laufzeitumgebung) |
|||
// "100-continue"-Mechanimus nach RFC 2616 deaktivieren, da von einigen Servern nicht unterstützt (Fehler "417 Expectation Failed.") |
|||
userAgent += " (" + Environment.OSVersion.VersionString + "; .NET CLR " + Environment.Version + ")"; |
|||
System.Net.ServicePointManager.Expect100Continue = false; |
|||
return userAgent; |
|||
} |
|||
private static string EncodeQuery(string[,] query) |
|||
{ |
|||
string result = ""; |
|||
AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName(); |
|||
for (int i = 0; i < query.GetLength(0); i++) |
|||
{ |
|||
result += "&" + query[i, 0]; |
|||
string value = query[i, 1]; |
|||
if (value != null) result += "=" + Uri.EscapeDataString(value); |
|||
} |
|||
return result; |
|||
} |
|||
private static string MultipartQuery(string[,] postData, string boundaryIdentifier) |
|||
// Programmname und -version |
|||
{ |
|||
String userAgent = assemblyName.Name + "/" + assemblyName.Version.Major + "." + assemblyName.Version.Minor; |
|||
StringBuilder sb = new StringBuilder(); |
|||
// Umgebungsinformationen (OS, Laufzeitumgebung) |
|||
for (int i = 0; i < postData.GetLength(0); i++) |
|||
userAgent += " (" + Environment.OSVersion.VersionString + "; .NET CLR " + Environment.Version + ")"; |
|||
{ |
|||
sb.Append("--"); |
|||
sb.Append(boundaryIdentifier); |
|||
sb.Append("\r\nContent-Disposition: form-data; name=\""); |
|||
sb.Append(postData[i, 0]); |
|||
sb.Append("\"\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n"); |
|||
sb.Append(postData[i, 1]); |
|||
sb.Append("\r\n"); |
|||
} |
|||
return sb.ToString(); |
|||
} |
|||
static HttpWebRequest GenerateHttpWebRequest(string language, string[,] query) |
|||
return userAgent; |
|||
{ |
|||
string url = "https://"; |
|||
if (language == "wikidata") url += "www.wikidata.org"; else url += language + ".wikipedia.org"; |
|||
url += "/w/api.php?format=xml"; |
|||
url += EncodeQuery(query); |
|||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); |
|||
// Limitierung: Parameterwert darf nicht länger als 32766 Bytes sein |
|||
private static String EncodeQuery(IEnumerable<KeyValuePair<String, object>> query) |
|||
request.UserAgent = userAgentString; |
|||
request.CookieContainer = cookieContainer; |
|||
request.AutomaticDecompression = DecompressionMethods.GZip; |
|||
request.KeepAlive = true; |
|||
return request; |
|||
} |
|||
public static HttpWebResponse HttpGet(string language, string[,] query) |
|||
{ |
|||
HttpWebRequest request = GenerateHttpWebRequest(language, query); |
|||
return (HttpWebResponse)request.GetResponse(); |
|||
} |
|||
public static HttpWebResponse HttpPost(string language, string[,] query, string[,] postData) |
|||
{ |
|||
HttpWebRequest request = GenerateHttpWebRequest(language, query); |
|||
request.Method = "POST"; |
|||
if (postData != null) |
|||
{ |
|||
string boundary = Guid.NewGuid().ToString(); |
|||
byte[] postContent = new UTF8Encoding(false).GetBytes(MultipartQuery(postData, boundary)); |
|||
request.ContentType = "multipart/form-data; boundary=" + boundary; |
|||
using (Stream stream = request.GetRequestStream()) |
|||
{ |
{ |
||
stream.Write(postContent, 0, postContent.Length); |
|||
.Select(x => { if (x.Value == null) return x.Key; else return x.Key + "=" + Uri.EscapeDataString(x.Value.ToString()); }) |
|||
.Aggregate((x, y) => x + "&" + y); |
|||
} |
} |
||
} |
|||
return (HttpWebResponse)request.GetResponse(); |
|||
} |
|||
public static void TraverseHttpWebResponse(HttpWebResponse response, Action<XmlReader> onXmlNode) |
|||
private static String MultipartQuery(IEnumerable<KeyValuePair<String, object>> query, String boundaryIdentifier) |
|||
{ |
|||
using (response) |
|||
using (Stream stream = response.GetResponseStream()) |
|||
using (XmlReader reader = XmlReader.Create(stream)) |
|||
{ |
|||
if (!reader.ReadToFollowing("api")) throw new MediawikiException("Malformed response"); |
|||
while (reader.Read()) |
|||
{ |
{ |
||
CheckForError(reader); |
|||
onXmlNode(reader); |
|||
foreach (KeyValuePair<String, object> kvp in query) |
|||
{ |
|||
sb.Append("--"); |
|||
sb.Append(boundaryIdentifier); |
|||
sb.Append("\r\nContent-Disposition: form-data; name=\""); |
|||
sb.Append(kvp.Key); |
|||
sb.Append("\"\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n"); |
|||
sb.Append(kvp.Value); |
|||
sb.Append("\r\n"); |
|||
} |
|||
return sb.ToString(); |
|||
} |
} |
||
} |
|||
} |
|||
public static string CalcMd5Hash(byte[] data) |
|||
Dictionary<String, object> GetDefaultParameters() |
|||
{ |
|||
System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); |
|||
byte[] md5Hash = md5.ComputeHash(data); |
|||
return BitConverter.ToString(md5Hash).Replace("-", "").ToLowerInvariant(); |
|||
} |
|||
public static void CheckForError(XmlReader reader) |
|||
{ |
|||
if (reader.NodeType == XmlNodeType.Element) |
|||
{ |
|||
if (reader.LocalName == "warnings") Debug.WriteLine(" warning: " + reader.ReadInnerXml()); |
|||
else if (reader.LocalName == "error") |
|||
{ |
{ |
||
string code = reader.GetAttribute("code"); |
|||
Dictionary<String, object> result = new Dictionary<String, object>() { { "format", "xml" }, { "maxlag", "5" } }; |
|||
string info = reader.GetAttribute("info"); |
|||
Debug.WriteLine(" error \"" + code + "\": " + info); |
|||
if (code == "maxlag") |
|||
throw new MediawikiLagException(code, info); |
|||
else |
|||
throw new MediawikiException(code, info); |
|||
} |
} |
||
} |
|||
} |
|||
public enum TokenType { Csrf, Watch, Patrol, Rollback, UserRights, Login, CreateAccount } |
|||
HttpWebRequest GenerateHttpWebRequest(Dictionary<String, object> query) |
|||
{ |
|||
String url = "https://" + language + ".wikipedia.org/w/api.php"; |
|||
if (query != null && query.Count > 0) |
|||
{ |
|||
url += "?" + EncodeQuery(GetDefaultParameters()); |
|||
url = String.Join("&", url, EncodeQuery(query)); |
|||
} |
|||
public static string GetToken(string lang, TokenType tokenType) |
|||
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest; |
|||
{ |
|||
request.UserAgent = userAgentString; |
|||
string tokenName = tokenType.ToString().ToLowerInvariant(); |
|||
request.CookieContainer = cookieContainer; |
|||
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; |
|||
string[,] paramList = new[,] { |
|||
request.KeepAlive = true; |
|||
{ "action", "query" }, |
|||
} |
{ "meta", "tokens" }, |
||
{ "type", tokenName } |
|||
}; |
|||
string token = null; |
|||
public HttpWebResponse HttpGet(Dictionary<String, object> query) |
|||
TraverseHttpWebResponse(HttpPost(lang, paramList, null), reader => |
|||
{ |
|||
if ((reader.NodeType == XmlNodeType.Element) && (reader.LocalName == "tokens")) |
|||
{ |
{ |
||
token = reader.GetAttribute(tokenName + "token"); |
|||
HttpWebRequest request = GenerateHttpWebRequest(query); |
|||
return request.GetResponse() as HttpWebResponse; |
|||
} |
} |
||
}); |
|||
return token; |
|||
} |
|||
public static void Login(string lang, NetworkCredential credentials) |
|||
public HttpWebResponse HttpPost(Dictionary<String, object> data) |
|||
{ |
|||
string[,] paramList = new[,] |
|||
{ |
|||
{ "action", "login" }, |
|||
{ "lgname", credentials.UserName }, |
|||
{ "lgpassword", credentials.Password }, |
|||
{ "lgtoken", GetToken(lang, TokenType.Login) } |
|||
}; |
|||
TraverseHttpWebResponse(HttpPost(lang, paramList, null), reader => |
|||
{ |
|||
if ((reader.NodeType == XmlNodeType.Element) && (reader.LocalName == "login")) |
|||
{ |
{ |
||
string result = reader.GetAttribute("result"); |
|||
if (result != "Success") |
|||
Dictionary<String, object> postData = new Dictionary<string, object>(GetDefaultParameters()); |
|||
{ |
|||
foreach (String key in data.Keys) postData.Add(key, data[key]); |
|||
if (result == "Throttled") result += $" (Please wait {reader.GetAttribute("wait")}s)"; |
|||
request.Method = "POST"; |
|||
throw new MediawikiException(result); |
|||
} |
|||
byte[] postContent = new UTF8Encoding(false).GetBytes(MultipartQuery(postData, boundary)); |
|||
request.ContentType = "multipart/form-data; boundary=" + boundary; |
|||
request.ContentLength = postContent.Length; |
|||
using (Stream stream = request.GetRequestStream()) stream.Write(postContent, 0, postContent.Length); |
|||
return request.GetResponse() as HttpWebResponse; |
|||
} |
} |
||
}); |
|||
} |
|||
public static void Logout(string lang) |
|||
{ |
|||
using (HttpWebResponse response = HttpGet(lang, new[,] { { "action", "logout" } })) { } |
|||
using (response) |
|||
} |
|||
using (Stream stream = response.GetResponseStream()) |
|||
using (XmlReader reader = XmlReader.Create(stream)) |
|||
{ |
|||
if (!reader.ReadToFollowing("api")) throw new MediawikiException("Malformed response"); |
|||
#endregion |
|||
while (reader.Read()) |
|||
} |
|||
{ |
|||
CheckForError(reader); |
|||
onXmlNode(reader); |
|||
} |
|||
} |
|||
} |
|||
public class Program |
|||
public static String CalcMd5Hash(byte[] data) |
|||
{ |
|||
static Mediawiki wiki; |
|||
static string editToken = null; |
|||
public class AnimeItem |
|||
{ |
|||
public string List; |
|||
public int Level; |
|||
public int Year; |
|||
public string Type; |
|||
public string EpisodeCount; |
|||
public string Title; |
|||
public string Link; |
|||
public string JapaneseTitle; |
|||
public string JapaneseTranscription; |
|||
public List<string> AlternativeTitles; |
|||
public List<string> Studios; |
|||
public static AnimeItem Parse(Dictionary<string, string> parsedTemplate, string list) |
|||
{ |
|||
AnimeItem result = new AnimeItem(); |
|||
try |
|||
{ |
{ |
||
result.List = list; |
|||
System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); |
|||
result.Level = Int32.Parse(parsedTemplate["1"]); |
|||
result.Year = Int32.Parse(parsedTemplate["2"]); |
|||
return BitConverter.ToString(md5Hash).Replace("-", "").ToLowerInvariant(); |
|||
result.Type = parsedTemplate["3"].Trim(); |
|||
result.EpisodeCount = parsedTemplate["4"].Trim(); |
|||
result.Title = parsedTemplate["5"].Trim(); |
|||
if (parsedTemplate.TryGetValue("link", out result.Link)) result.Link = result.Link.Trim(); |
|||
if (parsedTemplate.ContainsKey("j")) result.JapaneseTitle = parsedTemplate["j"].Trim(); else result.JapaneseTitle = ""; |
|||
if (parsedTemplate.ContainsKey("jt")) result.JapaneseTranscription = parsedTemplate["jt"].Trim(); else result.JapaneseTranscription = ""; |
|||
result.AlternativeTitles = new List<string>(); |
|||
result.Studios = new List<string>(); |
|||
for (int i = 1; i <= 10; i++) |
|||
{ |
|||
string paramName = "a" + i; |
|||
if (parsedTemplate.ContainsKey(paramName)) result.AlternativeTitles.Add(parsedTemplate[paramName].Trim()); |
|||
paramName = "s" + i; |
|||
if (parsedTemplate.ContainsKey(paramName)) result.Studios.Add(parsedTemplate[paramName].Trim()); |
|||
} |
|||
return result; |
|||
} |
} |
||
catch (KeyNotFoundException) { return null; } |
|||
catch (FormatException) { return null; } |
|||
} |
|||
public string GetWikilink() |
|||
{ |
|||
string result = "[["; |
|||
if (!string.IsNullOrEmpty(Link)) result += Link + "|"; |
|||
result += Title + "]]"; |
|||
return result; |
|||
} |
|||
public static string GetWikiArticle(string wikilink) |
|||
{ |
|||
if (string.IsNullOrEmpty(wikilink)) return ""; |
|||
else |
|||
{ |
{ |
||
// entferne Wikilinkklammern und splitte an der ersten Pipe |
|||
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "error")) |
|||
string link = wikilink.TrimStart('[').TrimEnd(']').Split(new char[] { '|' }, 2)[0]; |
|||
throw new MediawikiException(reader.GetAttribute("code"), reader.GetAttribute("info")); |
|||
// erstes Element (Zielartikel) mit Großbuchstaben beginnen lassen und zurückgeben |
|||
link = Char.ToUpperInvariant(link[0]) + link.Substring(1); |
|||
return link; |
|||
} |
} |
||
} |
|||
public string GetYearListEntry() |
|||
public void Login(String username, String password, String token = null) |
|||
{ |
|||
StringBuilder sb = new StringBuilder(); |
|||
sb.Append("{{Anime-Listeneintrag|"); |
|||
sb.Append(0); |
|||
sb.Append("|"); |
|||
sb.Append(Year); |
|||
sb.Append("|"); |
|||
sb.Append(Type); |
|||
sb.Append("|"); |
|||
sb.Append(EpisodeCount); |
|||
sb.Append("|"); |
|||
sb.Append(Title); |
|||
if (!string.IsNullOrEmpty(Link)) |
|||
{ |
{ |
||
sb.Append("|link="); |
|||
Dictionary<String, object> post = new Dictionary<String, object>() { |
|||
sb.Append(Link); |
|||
{ "lgname", username }, |
|||
{ "lgpassword", password } |
|||
}; |
|||
if (token != null) post.Add("lgtoken", token); |
|||
Boolean retryWithToken = false; |
|||
TraverseHttpWebResponse(HttpPost(post), delegate(XmlReader reader) |
|||
{ |
|||
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "login")) |
|||
{ |
|||
String result = reader.GetAttribute("result"); |
|||
if (result == "NeedToken") |
|||
{ |
|||
token = reader.GetAttribute("token"); |
|||
retryWithToken = true; |
|||
} |
|||
else if (result == "Success") { loggedIn = true; } |
|||
else throw new MediawikiException(result); |
|||
} |
|||
}); |
|||
if (retryWithToken) Login(username, password, token); |
|||
} |
} |
||
if (!string.IsNullOrEmpty(JapaneseTitle)) |
|||
public void Logout() |
|||
{ |
{ |
||
sb.Append("|j="); |
|||
using (HttpWebResponse response = HttpGet(new Dictionary<String, object> { { "action", "logout" } })) { } |
|||
sb.Append(JapaneseTitle); |
|||
} |
} |
||
if (!string.IsNullOrEmpty(JapaneseTranscription)) |
|||
public String GetEditToken(String pageTitle = null) |
|||
{ |
{ |
||
sb.Append("|jt="); |
|||
sb.Append(JapaneseTranscription); |
|||
TraverseHttpWebResponse(HttpGet(new Dictionary<String, object> { { "action", "query" }, { "meta", "tokens" }, { "type", "csrf" } }), delegate(XmlReader reader) |
|||
{ |
|||
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "tokens")) |
|||
{ |
|||
token = reader.GetAttribute("csrftoken"); |
|||
} |
|||
}); |
|||
return token; |
|||
} |
} |
||
for (int aIdx = 0; aIdx < AlternativeTitles.Count; aIdx++) |
|||
} |
|||
{ |
|||
sb.Append("|a"); |
|||
public class Program |
|||
sb.Append(aIdx + 1); |
|||
{ |
|||
sb.Append("="); |
|||
sb.Append(AlternativeTitles[aIdx]); |
|||
static String editToken = null; |
|||
} |
|||
for (int sIdx = 0; sIdx < Studios.Count; sIdx++) |
|||
public class AnimeItem |
|||
{ |
{ |
||
sb.Append("|s"); |
|||
sb.Append(sIdx + 1); |
|||
sb.Append("="); |
|||
sb.Append(Studios[sIdx]); |
|||
} |
|||
sb.Append("|mark=year}}"); |
|||
return sb.ToString(); |
|||
} |
|||
public String JapaneseTitle; |
|||
public String JapaneseTranscription; |
|||
public List<String> AlternativeTitles; |
|||
public List<String> Studios; |
|||
public override string ToString() |
|||
public static AnimeItem Parse(Dictionary<String, String> parsedTemplate, String list) |
|||
{ |
|||
return Title; |
|||
AnimeItem result = new AnimeItem(); |
|||
} |
|||
} |
|||
result.List = list; |
|||
result.Level = Int32.Parse(parsedTemplate["1"]); |
|||
result.Year = Int32.Parse(parsedTemplate["2"]); |
|||
result.Type = parsedTemplate["3"].Trim(); |
|||
result.EpisodeCount = parsedTemplate["4"].Trim(); |
|||
result.Title = parsedTemplate["5"].Trim(); |
|||
if (parsedTemplate.TryGetValue("link", out result.Link)) result.Link = result.Link.Trim(); |
|||
if (parsedTemplate.ContainsKey("j")) result.JapaneseTitle = parsedTemplate["j"].Trim(); else result.JapaneseTitle = ""; |
|||
if (parsedTemplate.ContainsKey("jt")) result.JapaneseTranscription = parsedTemplate["jt"].Trim(); else result.JapaneseTranscription = ""; |
|||
result.AlternativeTitles = new List<String>(); |
|||
result.Studios = new List<String>(); |
|||
for (int i = 1; i <= 10; i++) |
|||
{ |
|||
String paramName = "a" + i; |
|||
if (parsedTemplate.ContainsKey(paramName)) result.AlternativeTitles.Add(parsedTemplate[paramName].Trim()); |
|||
paramName = "s" + i; |
|||
if (parsedTemplate.ContainsKey(paramName)) result.Studios.Add(parsedTemplate[paramName].Trim()); |
|||
} |
|||
return result; |
|||
} |
|||
catch (KeyNotFoundException) { return null; } |
|||
catch (FormatException) { return null; } |
|||
} |
|||
#region Konfiguration |
|||
public String GetWikilink() |
|||
static UnicodeCategory[] validCharTypes = new UnicodeCategory[] { |
|||
{ |
|||
String result = "[["; |
|||
if (!String.IsNullOrEmpty(Link)) result += Link + "|"; |
|||
result += Title + "]]"; |
|||
return result; |
|||
} |
|||
public static String GetWikiArticle(String wikilink) |
|||
{ |
|||
if (String.IsNullOrEmpty(wikilink)) return ""; |
|||
else |
|||
{ |
|||
// entferne Wikilinkklammern und splitte an der ersten Pipe |
|||
String link = wikilink.TrimStart('[').TrimEnd(']').Split(new char[] { '|' }, 2)[0]; |
|||
// erstes Element (Zielartikel) mit Großbuchstaben beginnen lassen und zurückgeben |
|||
link = Char.ToUpperInvariant(link[0]) + link.Substring(1); |
|||
return link; |
|||
} |
|||
} |
|||
public String GetYearListEntry() |
|||
{ |
|||
StringBuilder sb = new StringBuilder(); |
|||
sb.Append("{{Anime-Listeneintrag|"); |
|||
sb.Append(0); |
|||
sb.Append("|"); |
|||
sb.Append(Year); |
|||
sb.Append("|"); |
|||
sb.Append(Type); |
|||
sb.Append("|"); |
|||
sb.Append(EpisodeCount); |
|||
sb.Append("|"); |
|||
sb.Append(Title); |
|||
if (!String.IsNullOrEmpty(Link)) |
|||
{ |
|||
sb.Append("|link="); |
|||
sb.Append(Link); |
|||
} |
|||
if (!String.IsNullOrEmpty(JapaneseTitle)) |
|||
{ |
|||
sb.Append("|j="); |
|||
sb.Append(JapaneseTitle); |
|||
} |
|||
if (!String.IsNullOrEmpty(JapaneseTranscription)) |
|||
{ |
|||
sb.Append("|jt="); |
|||
sb.Append(JapaneseTranscription); |
|||
} |
|||
for (int aIdx = 0; aIdx < AlternativeTitles.Count; aIdx++) |
|||
{ |
|||
sb.Append("|a"); |
|||
sb.Append(aIdx + 1); |
|||
sb.Append("="); |
|||
sb.Append(AlternativeTitles[aIdx]); |
|||
} |
|||
for (int sIdx = 0; sIdx < Studios.Count; sIdx++) |
|||
{ |
|||
sb.Append("|s"); |
|||
sb.Append(sIdx + 1); |
|||
sb.Append("="); |
|||
sb.Append(Studios[sIdx]); |
|||
} |
|||
sb.Append("|mark=year}}"); |
|||
return sb.ToString(); |
|||
} |
|||
public override string ToString() |
|||
{ |
|||
return Title; |
|||
} |
|||
} |
|||
#region Konfiguration |
|||
static UnicodeCategory[] validCharTypes = new UnicodeCategory[] { |
|||
UnicodeCategory.UppercaseLetter, UnicodeCategory.LowercaseLetter, UnicodeCategory.TitlecaseLetter, UnicodeCategory.ModifierLetter, UnicodeCategory.OtherLetter, |
UnicodeCategory.UppercaseLetter, UnicodeCategory.LowercaseLetter, UnicodeCategory.TitlecaseLetter, UnicodeCategory.ModifierLetter, UnicodeCategory.OtherLetter, |
||
UnicodeCategory.DecimalDigitNumber, UnicodeCategory.LetterNumber, UnicodeCategory.OtherNumber, UnicodeCategory.SpaceSeparator }; |
UnicodeCategory.DecimalDigitNumber, UnicodeCategory.LetterNumber, UnicodeCategory.OtherNumber, UnicodeCategory.SpaceSeparator }; |
||
static string[] alphaList = new string[] { "0–9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; |
|||
static List<int> yearList = new List<int> { 1900, 1945, 1946, 1962, 1963, 1969, 1970, 1979, 1980, 1984, 1985, 1989, 1990, 1994, 1995, 1997, 1998, 1999 }; // ab 2000 wird automatisch generiert |
|||
static Dictionary<string, string> animeTypes = new Dictionary<string, string> { |
|||
{ "F", "Film" }, |
{ "F", "Film" }, |
||
{ "S", "Fernsehserie" }, |
{ "S", "Fernsehserie" }, |
||
Zeile 341: | Zeile 375: | ||
{ "K", "Kurzfilm" } |
{ "K", "Kurzfilm" } |
||
}; |
}; |
||
#endregion |
|||
static Dictionary<string, string> ParseTemplate(string template) |
|||
{ |
|||
Dictionary<string, string> result = new Dictionary<string, string>(); |
|||
int startPos = template.IndexOf("{{"); |
|||
int lastPos = template.LastIndexOf("}}"); |
|||
if (startPos >= 0 && lastPos >= 0) |
|||
{ |
|||
string[] split = template.Substring(startPos + 2, lastPos - startPos - 2).Split('|'); |
|||
List<string> parameters = new List<string>(); |
|||
// Wikilinks wieder zusammenführen, d.h. "[[A", "B]]" zu "[[A|B]]". |
|||
int doubleBracketCount = 0; |
|||
for (int i = 0; i < split.Length; i++) |
|||
{ |
{ |
||
if (doubleBracketCount == 0) parameters.Add(split[i]); else parameters[parameters.Count - 1] += "|" + split[i]; |
|||
Dictionary<String, String> result = new Dictionary<String, String>(); |
|||
int curIdx = 0; |
|||
while (curIdx >= 0) |
|||
{ |
|||
curIdx = split[i].IndexOf("[[", curIdx); |
|||
if (curIdx >= 0) |
|||
{ |
{ |
||
doubleBracketCount++; |
|||
String[] split = template.Substring(startPos + 2, lastPos - startPos - 2).Split('|'); |
|||
curIdx += 2; |
|||
} |
|||
} |
|||
curIdx = 0; |
|||
while (curIdx >= 0) |
|||
{ |
|||
curIdx = split[i].IndexOf("]]", curIdx); |
|||
if (curIdx >= 0) |
|||
{ |
|||
doubleBracketCount--; |
|||
curIdx += 2; |
|||
} |
|||
} |
|||
} |
|||
for (int paramIdx = 0; paramIdx < parameters.Count; paramIdx++) |
|||
// Wikilinks wieder zusammenführen, d.h. "[[A", "B]]" zu "[[A|B]]". |
|||
{ |
|||
int doubleBracketCount = 0; |
|||
string[] keyValue = parameters[paramIdx].Split(new char[] { '=' }, 2); |
|||
for (int i = 0; i < split.Length; i++) |
|||
{ |
try |
||
{ |
|||
if (keyValue.Length == 2) result.Add(keyValue[0], keyValue[1]); else result.Add(paramIdx.ToString(), parameters[paramIdx]); |
|||
} |
|||
catch (ArgumentException) |
|||
{ |
|||
Console.Error.WriteLine("Doppelte Parameter: " + template); |
|||
} |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
// zur Sortierung alle Zeichen außer Buchstaben, Zahlen und Leerzeichen entfernen und Buchstaben in Kleinschreibung umwandeln |
|||
int curIdx = 0; |
|||
static string RemoveDiacriticsAndSpecialChars(string text) |
|||
while (curIdx >= 0) |
|||
{ |
|||
if (text.StartsWith("The ") || text.StartsWith("Der ") || text.StartsWith("Die ") || text.StartsWith("Das ")) text = text.Substring(4); |
|||
curIdx = split[i].IndexOf("[[", curIdx); |
|||
if (curIdx >= 0) |
|||
{ |
|||
doubleBracketCount++; |
|||
curIdx += 2; |
|||
} |
|||
} |
|||
curIdx = 0; |
|||
while (curIdx >= 0) |
|||
{ |
|||
curIdx = split[i].IndexOf("]]", curIdx); |
|||
if (curIdx >= 0) |
|||
{ |
|||
doubleBracketCount--; |
|||
curIdx += 2; |
|||
} |
|||
} |
|||
} |
|||
return string.Concat(text.Normalize(NormalizationForm.FormKD).Where(ch => validCharTypes.Contains(CharUnicodeInfo.GetUnicodeCategory(ch)))).Normalize(NormalizationForm.FormKC).ToLowerInvariant(); |
|||
for (int paramIdx = 0; paramIdx < parameters.Count; paramIdx++) |
|||
} |
|||
String[] keyValue = parameters[paramIdx].Split(new char[] { '=' }, 2); |
|||
try |
|||
{ |
|||
if (keyValue.Length == 2) result.Add(keyValue[0], keyValue[1]); else result.Add(paramIdx.ToString(), parameters[paramIdx]); |
|||
} |
|||
catch (ArgumentException) |
|||
{ |
|||
System.Console.Error.WriteLine("Doppelte Parameter: " + template); |
|||
} |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
static void WriteContent(string pagetitle, string content, string basetimestamp = null) |
|||
// zur Sortierung alle Zeichen außer Buchstaben, Zahlen und Leerzeichen entfernen und Buchstaben in Kleinschreibung umwandeln |
|||
{ |
|||
static String RemoveDiacriticsAndSpecialChars(String text) |
|||
if (editToken == null) editToken = Mediawiki.GetToken("de", Mediawiki.TokenType.Csrf); |
|||
bool badToken; |
|||
do |
|||
{ |
|||
badToken = false; |
|||
try |
|||
{ |
{ |
||
Console.Write("Schreibe " + pagetitle); |
|||
if (text.StartsWith("The ") || text.StartsWith("Der ") || text.StartsWith("Die ") || text.StartsWith("Das ")) text = text.Substring(4); |
|||
string md5Hash = Mediawiki.CalcMd5Hash(new UTF8Encoding(false).GetBytes(content)); |
|||
var post = new string[3 + (basetimestamp != null ? 1 : 0), 2]; |
|||
post[0, 0] = "token"; |
|||
post[0, 1] = editToken; |
|||
post[1, 0] = "text"; |
|||
post[1, 1] = content; |
|||
post[2, 0] = "md5"; |
|||
post[2, 1] = md5Hash; |
|||
if (basetimestamp != null) |
|||
{ |
|||
post[3, 0] = "basetimestamp"; |
|||
post[3, 1] = basetimestamp; |
|||
} |
|||
using (HttpWebResponse response = Mediawiki.HttpPost("de", new[,] { |
|||
return String.Concat(text.Normalize(NormalizationForm.FormKD).Where(ch => validCharTypes.Contains(CharUnicodeInfo.GetUnicodeCategory(ch)))).Normalize(NormalizationForm.FormKC).ToLowerInvariant(); |
|||
{ "assert", "user" }, |
|||
{ "action", "edit" }, |
|||
{ "title", pagetitle }, |
|||
{ "notminor", null }, |
|||
{ "summary", "Listen-Aktualisierung per [[Benutzer:Mps/AnimeListenUpdater.cs]]" } }, |
|||
post)) |
|||
using (Stream stream = response.GetResponseStream()) |
|||
using (XmlReader reader = XmlReader.Create(stream)) |
|||
{ |
|||
while (reader.Read()) Mediawiki.CheckForError(reader); |
|||
} |
|||
Console.WriteLine("."); |
|||
} |
} |
||
catch (MediawikiException mwe) |
|||
static void WriteContent(String pagetitle, String content, String basetimestamp = null) |
|||
{ |
{ |
||
Console.Error.WriteLine("\n" + pagetitle + ": " + mwe.Message); |
|||
if (editToken == null) editToken = wiki.GetEditToken(); |
|||
if (mwe.Code == "badtoken") |
|||
{ |
|||
// Die API wirft manchmal "badtoken", daher einfach Edit nochmal probieren. |
|||
Console.Error.WriteLine(" Neuer Versuch."); |
|||
badToken = true; |
|||
} |
|||
} |
|||
} while (badToken); |
|||
} |
|||
static bool BuildAnimeYearList(List<AnimeItem> animeList, int startYear, int endYear, TextWriter writer) |
|||
bool badToken; |
|||
{ |
|||
writer.WriteLine("__ABSCHNITTE_NICHT_BEARBEITEN__ __KEIN_INHALTSVERZEICHNIS__"); |
|||
{ |
|||
writer.WriteLine("Dies ist eine '''chronologisch sortierte Liste der Anime-Titel''' von " + startYear + " bis " + endYear + "."); |
|||
badToken = false; |
|||
writer.WriteLine("<!--"); |
|||
try |
|||
writer.WriteLine("##########"); |
|||
{ |
|||
writer.WriteLine("Bitte editiere diese Liste _nicht_, da sie von einem Bot automatisch generiert wird, der durchgeführte Änderungen überschreibt. Stattdessen können und sollten die alphabetisch sortierten Listen der Liste der Anime-Titel http://de.wikipedia.org/wiki/Liste_der_Anime-Titel bearbeitet werden, aus denen diese Liste generiert wird."); |
|||
System.Console.Write("Schreibe " + pagetitle); |
|||
writer.WriteLine("##########"); |
|||
String md5Hash = Mediawiki.CalcMd5Hash(new UTF8Encoding(false).GetBytes(content)); |
|||
writer.WriteLine("-->"); |
|||
writer.WriteLine("{{Navigationsleiste Liste der Anime-Titel}}"); |
|||
writer.WriteLine(); |
|||
bool hasAtLeastOneYear = false; |
|||
Dictionary<String, object> post = new Dictionary<String, object>() |
|||
for (int year = startYear; year <= endYear; year++) |
|||
{ |
|||
{ "action", "edit" }, |
|||
IEnumerable<AnimeItem> animesInYear = from anime in animeList |
|||
{ "title", pagetitle }, |
|||
where anime.Year == year |
|||
orderby RemoveDiacriticsAndSpecialChars(anime.Title) |
|||
select anime; |
|||
{ "md5", md5Hash }, |
|||
{ "text", content }, |
|||
{ "token", editToken } |
|||
}; |
|||
if (basetimestamp != null) post.Add("basetimestamp", basetimestamp); |
|||
if (animesInYear.Any()) |
|||
using (HttpWebResponse response = wiki.HttpPost(post)) |
|||
{ |
|||
using (Stream stream = response.GetResponseStream()) |
|||
hasAtLeastOneYear = true; |
|||
using (XmlReader reader = XmlReader.Create(stream)) |
|||
writer.WriteLine("== " + year + " =="); |
|||
writer.WriteLine("{{Anime-Listenkopf|"); |
|||
while (reader.Read()) Mediawiki.CheckForError(reader); |
|||
foreach (AnimeItem anime in animesInYear) |
|||
{ |
|||
System.Console.WriteLine("."); |
|||
writer.WriteLine(anime.GetYearListEntry()); |
|||
} |
|||
writer.WriteLine("|mark=year}}"); |
|||
writer.WriteLine(); |
|||
if (e.Message.Contains("badtoken")) |
|||
{ |
|||
// Die API wirft manchmal "badtoken", daher einfach Edit nochmal probieren. |
|||
System.Console.Error.WriteLine(" Neuer Versuch."); |
|||
badToken = true; |
|||
} |
|||
} |
|||
} while (badToken); |
|||
} |
} |
||
} |
|||
writer.WriteLine("[[Kategorie:Liste (Anime)|#" + startYear + "]]"); |
|||
return hasAtLeastOneYear; |
|||
static bool BuildAnimeYearList(List<AnimeItem> animeList, int startYear, int endYear, TextWriter writer) |
|||
} |
|||
writer.WriteLine("__ABSCHNITTE_NICHT_BEARBEITEN__ __KEIN_INHALTSVERZEICHNIS__"); |
|||
writer.WriteLine("Dies ist eine '''chronologisch sortierte Liste der Anime-Titel''' von " + startYear + " bis " + endYear + "."); |
|||
writer.WriteLine("<!--"); |
|||
writer.WriteLine("##########"); |
|||
writer.WriteLine("Bitte editiere diese Liste _nicht_, da sie von einem Bot automatisch generiert wird, der durchgeführte Änderungen überschreibt. Stattdessen können und sollten die alphabetisch sortierten Listen der Liste der Anime-Titel http://de.wikipedia.org/wiki/Liste_der_Anime-Titel bearbeitet werden, aus denen diese Liste generiert wird."); |
|||
writer.WriteLine("##########"); |
|||
writer.WriteLine("-->"); |
|||
writer.WriteLine("{{Navigationsleiste Liste der Anime-Titel}}"); |
|||
writer.WriteLine(); |
|||
static bool BuildAnimeStudioList(List<AnimeItem> animeList, TextWriter writer) |
|||
bool hasAtLeastOneYear = false; |
|||
{ |
|||
for (int year = startYear; year <= endYear; year++) |
|||
IEnumerable<string> linkedStudios = animeList.SelectMany(anime => anime.Studios) // Studiolisten zusammenführen |
|||
{ |
|||
.Where(studio => studio.StartsWith("[[")) // nach verlinkten Studios filtern |
|||
IEnumerable<AnimeItem> animesInYear = from anime in animeList |
|||
.Select(studio => AnimeItem.GetWikiArticle(studio)) // Linkklammern entfernen, nach Pipe separieren und nur erstes Element (Linkziel) zurückgeben |
|||
.Distinct() // doppelte Einträge entfernen |
|||
.OrderBy(name => RemoveDiacriticsAndSpecialChars(name)); // sortieren |
|||
foreach (string curStudio in linkedStudios) |
|||
if (animesInYear.Any()) |
|||
{ |
|||
writer.WriteLine("{{#ifeq: {{{1}}}|" + curStudio + "|"); |
|||
hasAtLeastOneYear = true; |
|||
writer.WriteLine("== " + year + " =="); |
|||
writer.WriteLine("{{Anime-Listenkopf|"); |
|||
foreach (AnimeItem anime in animesInYear) |
|||
{ |
|||
writer.WriteLine(anime.GetYearListEntry()); |
|||
} |
|||
writer.WriteLine("|mark=year}}"); |
|||
writer.WriteLine(); |
|||
} |
|||
} |
|||
writer.WriteLine("[[Kategorie:Liste (Anime)|#" + startYear + "]]"); |
|||
Dictionary<string, List<AnimeItem>> animesOfStudio = animeList |
|||
return hasAtLeastOneYear; |
|||
.Where(anime => anime.Studios.Any(studio => AnimeItem.GetWikiArticle(studio) == curStudio)) // nach Anime die vom aktuellen Studio gefertigt wurden filtern |
|||
} |
|||
.OrderBy(anime => anime.Year + RemoveDiacriticsAndSpecialChars(anime.Title)) // nach Jahr und Titel sortieren |
|||
.GroupBy(anime => anime.Type) // nach Typ gruppieren |
|||
.ToDictionary(group => group.Key, group => group.ToList()); // in assoziative Liste überführen |
|||
// Generierungsregeln: |
|||
static bool BuildAnimeStudioList(List<AnimeItem> animeList, TextWriter writer) |
|||
// ab 10 Einträge pro Typ: eigene Gruppe (Reihenfolge: Fernsehserien, Filme, Original Video Animations, Specials, Kurzfilme, Weitere Anime-Produktionen) |
|||
// ab 10 Einträge pro Gruppe: zwei Spalten, wobei bei ungerader Anzahl die erste Spalte länger ist |
|||
bool hasCaptions = false; |
|||
foreach (string animeType in animeTypes.Keys) |
|||
{ |
{ |
||
#region wenn ein Studio mehr als 10 Animes eines Typs produziert hat, diese separat aufführen |
|||
IEnumerable<String> linkedStudios = animeList.SelectMany(anime => anime.Studios) // Studiolisten zusammenführen |
|||
if (animesOfStudio.ContainsKey(animeType) && animesOfStudio[animeType].Count >= 10) |
|||
.Where(studio => studio.StartsWith("[[")) // nach verlinkten Studios filtern |
|||
{ |
|||
.Select(studio => AnimeItem.GetWikiArticle(studio)) // Linkklammern entfernen, nach Pipe separieren und nur erstes Element (Linkziel) zurückgeben |
|||
hasCaptions = true; |
|||
.Distinct() // doppelte Einträge entfernen |
|||
switch (animeType) |
|||
.OrderBy(name => RemoveDiacriticsAndSpecialChars(name)); // sortieren |
|||
{ |
|||
case "S": |
|||
writer.WriteLine("=== Fernsehserien ==="); |
|||
break; |
|||
case "F": |
|||
writer.WriteLine("=== Filme ==="); |
|||
break; |
|||
case "W": |
|||
writer.WriteLine("=== Web-Animes ==="); |
|||
break; |
|||
case "O": |
|||
writer.WriteLine("=== Original Video Animations ==="); |
|||
break; |
|||
case "SP": |
|||
writer.WriteLine("=== Specials ==="); |
|||
break; |
|||
case "K": |
|||
writer.WriteLine("=== Kurzfilme ==="); |
|||
break; |
|||
} |
|||
writer.WriteLine("{{(!}} class=\"toptextcells\""); |
|||
writer.WriteLine("{{!}}"); |
|||
int newColIdx = (int)Math.Ceiling(animesOfStudio[animeType].Count / 2.0); |
|||
foreach (String curStudio in linkedStudios) |
|||
int animeIdx = 0; |
|||
foreach (AnimeItem anime in animesOfStudio[animeType]) |
|||
{ |
{ |
||
writer.WriteLine("* " + anime.Year + ": ''" + anime.GetWikilink() + "''"); |
|||
animeIdx++; |
|||
if (animeIdx == newColIdx) writer.WriteLine("{{!}}"); |
|||
} |
|||
writer.WriteLine("{{!)}}"); |
|||
Dictionary<String, List<AnimeItem>> animesOfStudio = animeList |
|||
.Where(anime => anime.Studios.Any(studio => AnimeItem.GetWikiArticle(studio) == curStudio)) // nach Anime die vom aktuellen Studio gefertigt wurden filtern |
|||
.OrderBy(anime => anime.Year + RemoveDiacriticsAndSpecialChars(anime.Title)) // nach Jahr und Titel sortieren |
|||
.GroupBy(anime => anime.Type) // nach Typ gruppieren |
|||
.ToDictionary(group => group.Key, group => group.ToList()); // in assoziative Liste überführen |
|||
animesOfStudio.Remove(animeType); |
|||
} |
|||
// ab 10 Einträge pro Typ: eigene Gruppe (Reihenfolge: Fernsehserien, Filme, Original Video Animations, Specials, Kurzfilme, Weitere Anime-Produktionen) |
|||
#endregion |
|||
// ab 10 Einträge pro Gruppe: zwei Spalten, wobei bei ungerader Anzahl die erste Spalte länger ist |
|||
} |
|||
bool hasCaptions = false; |
|||
foreach (String animeType in animeTypes.Keys) |
|||
{ |
|||
#region wenn ein Studio mehr als 10 Animes eines Typs produziert hat, diese separat aufführen |
|||
if (animesOfStudio.ContainsKey(animeType) && animesOfStudio[animeType].Count >= 10) |
|||
{ |
|||
hasCaptions = true; |
|||
switch (animeType) |
|||
{ |
|||
case "S": |
|||
writer.WriteLine("=== Fernsehserien ==="); |
|||
break; |
|||
case "F": |
|||
writer.WriteLine("=== Filme ==="); |
|||
break; |
|||
case "W": |
|||
writer.WriteLine("=== Web-Animes ==="); |
|||
break; |
|||
case "O": |
|||
writer.WriteLine("=== Original Video Animations ==="); |
|||
break; |
|||
case "SP": |
|||
writer.WriteLine("=== Specials ==="); |
|||
break; |
|||
case "K": |
|||
writer.WriteLine("=== Kurzfilme ==="); |
|||
break; |
|||
} |
|||
writer.WriteLine("{{(!}} class=\"toptextcells\""); |
|||
writer.WriteLine("{{!}}"); |
|||
IEnumerable<AnimeItem> otherAnimes = animesOfStudio |
|||
.SelectMany(kvp => kvp.Value) |
|||
.OrderBy(anime => anime.Year + RemoveDiacriticsAndSpecialChars(anime.Title)); |
|||
foreach (AnimeItem anime in animesOfStudio[animeType]) |
|||
{ |
|||
writer.WriteLine("* " + anime.Year + ": ''" + anime.GetWikilink() + "''"); |
|||
animeIdx++; |
|||
if (animeIdx == newColIdx) writer.WriteLine("{{!}}"); |
|||
} |
|||
if (otherAnimes.Any()) |
|||
{ |
|||
if (hasCaptions) writer.WriteLine("=== Weitere Anime-Produktionen ==="); |
|||
int newColIdx = (int)Math.Ceiling(otherAnimes.Count() / 2.0); |
|||
if (hasCaptions || newColIdx >= 5) |
|||
{ |
|||
writer.WriteLine("{{(!}} class=\"toptextcells\""); |
|||
writer.WriteLine("{{!}}"); |
|||
} |
|||
else newColIdx = -1; |
|||
int animeIdx = 0; |
|||
foreach (AnimeItem anime in otherAnimes) |
|||
{ |
|||
string desc; |
|||
if (animeTypes.ContainsKey(anime.Type)) desc = " (" + animeTypes[anime.Type] + ")"; else desc = ""; |
|||
writer.WriteLine("* " + anime.Year + ": ''" + anime.GetWikilink() + "''" + desc); |
|||
animeIdx++; |
|||
if (animeIdx == newColIdx) writer.WriteLine("{{!}}"); |
|||
} |
|||
if (hasCaptions || newColIdx >= 5) writer.WriteLine("{{!)}}"); |
|||
IEnumerable<AnimeItem> otherAnimes = animesOfStudio |
|||
} |
|||
.SelectMany(kvp => kvp.Value) |
|||
writer.Write("}}"); |
|||
.OrderBy(anime => anime.Year + RemoveDiacriticsAndSpecialChars(anime.Title)); |
|||
} |
|||
writer.Write("<div class=\"noprint\" style=\"padding-left:1em;\"><small>''Diese Liste wird aus den Einträgen der [[Liste der Anime-Titel]] generiert. Einträge können und sollten dort ergänzt werden.</small></div>"); |
|||
if (otherAnimes.Any()) |
|||
writer.Write("<noinclude>[[Kategorie:Vorlage:Film und Fernsehen]]</noinclude>"); |
|||
{ |
|||
if (hasCaptions) writer.WriteLine("=== Weitere Anime-Produktionen ==="); |
|||
int newColIdx = (int)Math.Ceiling(otherAnimes.Count() / 2.0); |
|||
if (hasCaptions || newColIdx >= 5) |
|||
{ |
|||
writer.WriteLine("{{(!}} class=\"toptextcells\""); |
|||
writer.WriteLine("{{!}}"); |
|||
} |
|||
else newColIdx = -1; |
|||
return true; |
|||
int animeIdx = 0; |
|||
} |
|||
foreach (AnimeItem anime in otherAnimes) |
|||
{ |
|||
String desc; |
|||
if (animeTypes.ContainsKey(anime.Type)) desc = " (" + animeTypes[anime.Type] + ")"; else desc = ""; |
|||
writer.WriteLine("* " + anime.Year + ": ''" + anime.GetWikilink() + "''" + desc); |
|||
animeIdx++; |
|||
if (animeIdx == newColIdx) writer.WriteLine("{{!}}"); |
|||
} |
|||
static bool BuildMaintainenceInfo(List<AnimeItem> animeList, TextWriter writer) |
|||
if (hasCaptions || newColIdx >= 5) writer.WriteLine("{{!)}}"); |
|||
{ |
|||
var allStudios = animeList.SelectMany(anime => anime.Studios, (anime, studio) => new { Name = studio, List = anime.List }); |
|||
writer.Write("}}"); |
|||
} |
|||
var linkedStudios = allStudios |
|||
writer.Write("<div class=\"noprint\" style=\"padding-left:1em;\"><small>''Diese Liste wird aus den Einträgen der [[Liste der Anime-Titel]] generiert. Einträge können und sollten dort ergänzt werden.</small></div>"); |
|||
.Where(studio => studio.Name.StartsWith("[[")) |
|||
writer.Write("<noinclude>[[Kategorie:Vorlage:Film und Fernsehen]]</noinclude>"); |
|||
.Select(studio => new { Name = AnimeItem.GetWikiArticle(studio.Name), List = studio.List }) |
|||
.GroupBy(studio => studio.Name) |
|||
.Select(studio => new { Name = studio.Key, Count = studio.Count(), Lists = studio.Select(list => list.List).Distinct().OrderBy(list => list) }) |
|||
.OrderBy(studio => studio.Name); |
|||
var unlinkedStudios = allStudios |
|||
.Where(studio => !studio.Name.StartsWith("[[")) |
|||
.Select(studio => new { Name = studio.Name, List = studio.List }) |
|||
.GroupBy(studio => studio.Name) |
|||
.Select(studio => new { Name = studio.Key, Count = studio.Count(), Lists = studio.Select(list => list.List).Distinct().OrderBy(list => list) }); |
|||
writer.WriteLine("== Nur teilweise verlinkte Studios =="); |
|||
writer.WriteLine("{| class=\"wikitable sortable\""); |
|||
writer.WriteLine("! Studio || Auftreten || Listen"); |
|||
foreach (var linkedStudio in linkedStudios) |
|||
{ |
|||
var match = unlinkedStudios.FirstOrDefault(unlinkedStudio => unlinkedStudio.Name == linkedStudio.Name); |
|||
if (match != null) |
|||
{ |
|||
writer.WriteLine("|-\n| {0} || {1} || {2}", linkedStudio.Name, match.Count, string.Join(", ", match.Lists.Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]"))); |
|||
} |
} |
||
} |
|||
writer.WriteLine("|}\n"); |
|||
writer.WriteLine("== Potentiell verlinkbare Studios =="); |
|||
static bool BuildMaintainenceInfo(List<AnimeItem> animeList, TextWriter writer) |
|||
writer.WriteLine("{| class=\"wikitable sortable\""); |
|||
writer.WriteLine("! Studio || Anzahl || Listen"); |
|||
foreach (var unlinkedStudio in unlinkedStudios) |
|||
{ |
|||
if (!string.IsNullOrEmpty(unlinkedStudio.Name) && unlinkedStudio.Count > 5 && linkedStudios.All(linkedStudio => linkedStudio.Name != unlinkedStudio.Name)) |
|||
{ |
{ |
||
writer.WriteLine("|-\n| {0} || {1} || {2}", unlinkedStudio.Name, unlinkedStudio.Count, string.Join(", ", unlinkedStudio.Lists.Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]"))); |
|||
var allStudios = animeList.SelectMany(anime => anime.Studios, (anime, studio) => new { Name = studio, List = anime.List }); |
|||
} |
|||
} |
|||
writer.WriteLine("|}\n"); |
|||
Dictionary<string, List<string>> combinedStudioList = allStudios |
|||
.Where(studio => !string.IsNullOrEmpty(studio.Name)) |
|||
.Select(studio => new { Name = AnimeItem.GetWikiArticle(studio.Name), List = studio.List }) |
|||
.GroupBy(studio => studio.Name) |
|||
.OrderBy(studio => studio.Key) |
|||
.ToDictionary(group => group.Key, group => group.Select(studio => studio.List).Distinct().ToList()); |
|||
writer.WriteLine("== Ähnliche Studionamen =="); |
|||
var unlinkedStudios = allStudios |
|||
writer.WriteLine("{| class=\"wikitable sortable\""); |
|||
.Where(studio => !studio.Name.StartsWith("[[")) |
|||
writer.WriteLine("! Studio || Ähnliche Namen || Listen"); |
|||
.Select(studio => new { Name = studio.Name, List = studio.List }) |
|||
string[] allStudiosArray = combinedStudioList.Keys.ToArray(); |
|||
.GroupBy(studio => studio.Name) |
|||
for (int i = 0; i < allStudiosArray.Length; i++) |
|||
.Select(studio => new { Name = studio.Key, Count = studio.Count(), Lists = studio.Select(list => list.List).Distinct().OrderBy(list => list) }); |
|||
{ |
|||
string targetStudio = allStudiosArray[i]; |
|||
Dictionary<string, int> similarStudios = new Dictionary<string, int>(); |
|||
for (int j = 0/*i + 1*/; j < allStudiosArray.Length; j++) |
|||
{ |
|||
if (i == j) continue; |
|||
string otherStudio = allStudiosArray[j]; |
|||
writer.WriteLine("== Nur teilweise verlinkte Studios =="); |
|||
writer.WriteLine("{| class=\"wikitable sortable\""); |
|||
writer.WriteLine("! Studio || Auftreten || Listen"); |
|||
foreach (var linkedStudio in linkedStudios) |
|||
{ |
|||
var match = unlinkedStudios.FirstOrDefault(unlinkedStudio => unlinkedStudio.Name == linkedStudio.Name); |
|||
int dist = LevenshteinDistance(RemoveDiacriticsAndSpecialChars(targetStudio), RemoveDiacriticsAndSpecialChars(otherStudio)); |
|||
if (match != null) |
|||
if (dist <= 2) |
|||
{ |
|||
writer.WriteLine("|-\n| {0} || {1} || {2}", linkedStudio.Name, match.Count, String.Join(", ", match.Lists.Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]"))); |
|||
similarStudios.Add(otherStudio, dist); |
|||
} |
|||
} |
|||
if (similarStudios.Count > 0) |
|||
{ |
|||
writer.Write("|-\n| rowspan=\"{0}\" | {1} |", similarStudios.Count, targetStudio); |
|||
foreach (string similarName in similarStudios.OrderBy(item => item.Value).Select(item => item.Key)) |
|||
writer.WriteLine("== Potentiell verlinkbare Studios =="); |
|||
{ |
|||
writer.WriteLine("{| class=\"wikitable sortable\""); |
|||
writer.WriteLine(" |
writer.WriteLine("| {0} || {1}\r\n|-", similarName, |
||
string.Join(", ", combinedStudioList[similarName].Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]"))); |
|||
foreach (var unlinkedStudio in unlinkedStudios) |
|||
} |
|||
} |
|||
if (!String.IsNullOrEmpty(unlinkedStudio.Name) && unlinkedStudio.Count > 5 && linkedStudios.All(linkedStudio => linkedStudio.Name != unlinkedStudio.Name)) |
|||
} |
|||
writer.WriteLine("|}"); |
|||
writer.WriteLine("|-\n| {0} || {1} || {2}", unlinkedStudio.Name, unlinkedStudio.Count, String.Join(", ", unlinkedStudio.Lists.Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]"))); |
|||
} |
|||
} |
|||
writer.WriteLine("|}\n"); |
|||
return true; |
|||
Dictionary<String, List<String>> combinedStudioList = allStudios |
|||
} |
|||
.Where(studio => !String.IsNullOrEmpty(studio.Name)) |
|||
.Select(studio => new { Name = AnimeItem.GetWikiArticle(studio.Name), List = studio.List }) |
|||
.GroupBy(studio => studio.Name) |
|||
.OrderBy(studio => studio.Key) |
|||
.ToDictionary(group => group.Key, group => group.Select(studio => studio.List).Distinct().ToList()); |
|||
static int LevenshteinDistance(string source, string target) |
|||
writer.WriteLine("== Ähnliche Studionamen =="); |
|||
{ |
|||
writer.WriteLine("{| class=\"wikitable sortable\""); |
|||
if (string.IsNullOrEmpty(source) && string.IsNullOrEmpty(target)) return 0; |
|||
writer.WriteLine("! Studio || Ähnliche Namen || Listen"); |
|||
if (string.IsNullOrEmpty(source)) return target.Length; |
|||
String[] allStudiosArray = combinedStudioList.Keys.ToArray(); |
|||
if (string.IsNullOrEmpty(target)) return source.Length; |
|||
for (int i = 0; i < allStudiosArray.Length; i++) |
|||
{ |
|||
String targetStudio = allStudiosArray[i]; |
|||
Dictionary<String, int> similarStudios = new Dictionary<String, int>(); |
|||
for (int j = 0/*i + 1*/; j < allStudiosArray.Length; j++) |
|||
{ |
|||
if (i == j) continue; |
|||
int[,] d = new int[source.Length + 1, target.Length + 1]; |
|||
String otherStudio = allStudiosArray[j]; |
|||
for (int i = 1; i <= source.Length; i++) d[i, 0] = i; |
|||
int dist = LevenshteinDistance(RemoveDiacriticsAndSpecialChars(targetStudio), RemoveDiacriticsAndSpecialChars(otherStudio)); |
|||
for (int j = 1; j <= target.Length; j++) d[0, j] = j; |
|||
{ |
|||
similarStudios.Add(otherStudio, dist); |
|||
} |
|||
} |
|||
if (similarStudios.Count > 0) |
|||
{ |
|||
writer.Write("|-\n| rowspan=\"{0}\" | {1} |", similarStudios.Count, targetStudio); |
|||
foreach (String similarName in similarStudios.OrderBy(item => item.Value).Select(item => item.Key)) |
|||
{ |
|||
writer.WriteLine("| {0} || {1}\r\n|-", similarName, |
|||
String.Join(", ", combinedStudioList[similarName].Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]"))); |
|||
} |
|||
} |
|||
} |
|||
writer.WriteLine("|}"); |
|||
for (int j = 1; j <= target.Length; j++) |
|||
{ |
|||
for (int i = 1; i <= source.Length; i++) |
|||
static int LevenshteinDistance(string source, string target) |
|||
{ |
{ |
||
if (source[i - 1] == target[j - 1]) |
|||
d[i, j] = d[i - 1, j - 1]; |
|||
else |
|||
if (String.IsNullOrEmpty(target)) return source.Length; |
|||
d[i, j] = new int[] { |
|||
int[,] d = new int[source.Length + 1, target.Length + 1]; |
|||
for (int i = 1; i <= source.Length; i++) d[i, 0] = i; |
|||
for (int j = 1; j <= target.Length; j++) d[0, j] = j; |
|||
for (int j = 1; j <= target.Length; j++) |
|||
{ |
|||
for (int i = 1; i <= source.Length; i++) |
|||
{ |
|||
if (source[i - 1] == target[j - 1]) |
|||
d[i, j] = d[i - 1, j - 1]; |
|||
else |
|||
d[i, j] = new int[] { |
|||
d[i - 1, j] + 1, // Löschung |
d[i - 1, j] + 1, // Löschung |
||
d[i, j - 1] + 1, // Einfügung |
d[i, j - 1] + 1, // Einfügung |
||
d[i - 1, j - 1] + 1 // Ersetzung |
d[i - 1, j - 1] + 1 // Ersetzung |
||
}.Min(); |
}.Min(); |
||
} |
|||
} |
|||
return d[source.Length, target.Length]; |
|||
} |
} |
||
} |
|||
return d[source.Length, target.Length]; |
|||
struct ContentAndTimestamp |
|||
} |
|||
struct ContentAndTimestamp |
|||
{ |
|||
public string Content; |
|||
public string Timestamp; |
|||
} |
|||
private static NetworkCredential GetCredentials() |
|||
{ |
|||
Console.Write("Benutzer: "); |
|||
string user = Console.ReadLine(); |
|||
Console.Write("Passwort: "); |
|||
string password = string.Empty; |
|||
// Sternchen zeigen, statt eingetipptem Passwort |
|||
while (true) |
|||
{ |
|||
ConsoleKeyInfo keyInfo = Console.ReadKey(true); |
|||
if (keyInfo.Key == ConsoleKey.Enter) |
|||
{ |
{ |
||
Console.WriteLine(); |
|||
break; |
|||
} |
} |
||
else if (keyInfo.Key == ConsoleKey.Backspace) |
|||
{ |
|||
if (password.Length > 0) |
|||
{ |
|||
password = password.Remove(password.Length - 1); |
|||
// bei Backspace: Cursor eine Position zurück, mit Leerzeichen überschreiben, wieder eine Position zurück |
|||
Console.Write(keyInfo.KeyChar + " " + keyInfo.KeyChar); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
password += keyInfo.KeyChar; |
|||
Console.Write("*"); |
|||
} |
|||
} |
|||
Console.WriteLine(); |
|||
return new NetworkCredential(user, password); |
|||
} |
|||
static void Main(string[] args) |
|||
{ |
|||
string matchStudiosFilename = null; |
|||
NetworkCredential credentials = null; |
|||
#region Kommandozeilenparameter auswerten |
|||
string user = null, password = null; |
|||
for (int i = 0; i < args.Length; i++) |
|||
{ |
|||
if (args[i] == "-?") |
|||
{ |
{ |
||
Console.WriteLine(Assembly.GetExecutingAssembly().GetName() + " [Parameter]"); |
|||
String user = null; |
|||
Console.WriteLine(); |
|||
Console.WriteLine("-user Benutzername Benutzername angeben"); |
|||
String matchStudiosFilename = null; |
|||
Console.WriteLine("-pass Passwort Passwort angeben"); |
|||
#region Kommandozeilenparameter auswerten |
|||
Console.WriteLine("-matchstudios Datei Studionamen in den Jahreslisten angleichen."); |
|||
for (int i = 0; i < args.Length; i++) |
|||
Console.WriteLine(" Eingabe ist eine UTF8-kodierte Datei die zeilenweise die Ersetzungen angibt im Format: Ist<TAB>Soll"); |
|||
{ |
|||
return; |
|||
} |
|||
else |
|||
if (i + 1 < args.Length) |
|||
System.Console.WriteLine(Assembly.GetExecutingAssembly().GetName() + " [Parameter]"); |
|||
{ |
|||
System.Console.WriteLine(); |
|||
switch (args[i]) |
|||
System.Console.WriteLine("-user Benutzername Benutzername angeben"); |
|||
{ |
|||
System.Console.WriteLine("-pass Passwort Passwort angeben"); |
|||
case "-user": |
|||
System.Console.WriteLine("-matchstudios Datei Studionamen in den Jahreslisten angleichen."); |
|||
user = args[i + 1]; |
|||
System.Console.WriteLine(" Eingabe ist eine UTF8-kodierte Datei die zeilenweise die Ersetzungen angibt im Format: Ist<TAB>Soll"); |
|||
i++; |
|||
break; |
|||
case "-pass": |
|||
password = args[i + 1]; |
|||
i++; |
|||
break; |
|||
case "-matchstudios": |
|||
matchStudiosFilename = args[i + 1]; |
|||
if (!File.Exists(matchStudiosFilename)) |
|||
{ |
|||
Console.WriteLine("Datei nicht gefunden!"); |
|||
return; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
case "-matchstudios": |
|||
} |
|||
matchStudiosFilename = args[i + 1]; |
|||
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password)) { credentials = new NetworkCredential(user, password); } |
|||
if (!File.Exists(matchStudiosFilename)) |
|||
#endregion |
|||
{ |
|||
System.Console.WriteLine("Datei nicht gefunden!"); |
|||
return; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
#endregion |
|||
wiki = new Mediawiki(); |
|||
#region Nutzername und Passwort für Edit |
|||
try |
|||
{ |
|||
if (credentials == null) { credentials = GetCredentials(); } |
|||
Mediawiki.Login("de", credentials); |
|||
user = System.Console.ReadLine(); |
|||
} |
|||
if (password == null) |
|||
{ |
|||
System.Console.Write("Passwort: "); |
|||
password = ""; |
|||
// Sternchen zeigen, statt eingetipptem Passwort |
|||
while (true) |
|||
{ |
|||
ConsoleKeyInfo keyInfo = System.Console.ReadKey(true); |
|||
if (keyInfo.Key == ConsoleKey.Enter) |
|||
{ |
|||
System.Console.WriteLine(); |
|||
break; |
|||
} |
|||
else |
|||
if (keyInfo.Key == ConsoleKey.Backspace) |
|||
{ |
|||
if (password.Length > 0) |
|||
{ |
|||
password = password.Remove(password.Length - 1); |
|||
// bei Backspace: Cursor eine Position zurück, mit Leerzeichen überschreiben, wieder eine Position zurück |
|||
System.Console.Write(keyInfo.KeyChar + " " + keyInfo.KeyChar); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
password += keyInfo.KeyChar; |
|||
System.Console.Write("*"); |
|||
} |
|||
} |
|||
System.Console.WriteLine(); |
|||
} |
|||
#endregion |
|||
#region Eingabelisten lesen |
|||
wiki = new Mediawiki("de"); |
|||
Console.WriteLine("Alphabetische Listen lesen:"); |
|||
try |
|||
Console.Write(" "); |
|||
Dictionary<string, ContentAndTimestamp> alphabeticLists = new Dictionary<string, ContentAndTimestamp>(); |
|||
wiki.Login(user, password); |
|||
Parallel.ForEach(alphaList, letter => |
|||
#region Eingabelisten lesen |
|||
{ |
|||
System.Console.WriteLine("Alphabetische Listen lesen:"); |
|||
Mediawiki.TraverseHttpWebResponse(Mediawiki.HttpGet("de", new[,] { |
|||
System.Console.Write(" "); |
|||
Dictionary<String, ContentAndTimestamp> alphabeticLists = new Dictionary<String, ContentAndTimestamp>(); |
|||
Parallel.ForEach(alphaList, letter => |
|||
{ |
|||
Mediawiki.TraverseHttpWebResponse(wiki.HttpGet(new Dictionary<String, object> { |
|||
{ "action", "query" }, |
{ "action", "query" }, |
||
{ "prop", "revisions" }, |
{ "prop", "revisions" }, |
||
{ "titles", "Liste der Anime-Titel/" + letter }, |
{ "titles", "Liste der Anime-Titel/" + letter }, |
||
{ "rvprop", "content|timestamp" } }), |
{ "rvprop", "content|timestamp" } }), |
||
delegate (XmlReader reader) |
|||
{ |
|||
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "rev")) |
|||
{ |
|||
string curTimestamp = reader.GetAttribute("timestamp"); |
|||
string content = reader.ReadString(); |
|||
lock (alphabeticLists) |
|||
{ |
|||
alphabeticLists.Add(letter, new ContentAndTimestamp() { Content = content, Timestamp = curTimestamp }); |
|||
System.Console.Write(letter); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
System.Console.WriteLine(); |
|||
#endregion |
|||
if (matchStudiosFilename != null) |
|||
{ |
{ |
||
alphabeticLists.Add(letter, new ContentAndTimestamp() { Content = content, Timestamp = curTimestamp }); |
|||
#region Studionamen ersetzen |
|||
Console.Write(letter); |
|||
Dictionary<Regex, String> replacers = new Dictionary<Regex, String>(); |
|||
foreach (String line in File.ReadAllLines(matchStudiosFilename, Encoding.UTF8)) |
|||
{ |
|||
String[] items = line.Split('\t'); |
|||
if (items.Length >= 2) replacers.Add(new Regex(@"(\|\s*s[0-9]+\s*=\s*\[*)" + Regex.Escape(items[0]) + @"(\s*[\|}\]])", RegexOptions.Singleline | RegexOptions.CultureInvariant), items[1]); |
|||
System.Console.WriteLine(" " + items[0] + " -> " + items[1]); |
|||
} |
|||
System.Console.Write("Fortfahren [j/n]: "); |
|||
if (System.Console.ReadLine().ToLowerInvariant() == "j") |
|||
{ |
|||
foreach (String letter in alphabeticLists.Keys.OrderBy(key => key)) |
|||
{ |
|||
String content = alphabeticLists[letter].Content; |
|||
foreach (Regex replace in replacers.Keys) content = replace.Replace(content, "$1" + replacers[replace] + "$2"); |
|||
WriteContent("Liste der Anime-Titel/" + letter, content, alphabeticLists[letter].Timestamp); |
|||
} |
|||
} |
|||
#endregion |
|||
} |
} |
||
} |
|||
}); |
|||
}); |
|||
#region Eingabelisten parsen |
|||
Console.WriteLine(); |
|||
List<AnimeItem> animeList = new List<AnimeItem>(); |
|||
#endregion |
|||
Regex templateRegex = new Regex(@"\{\{\s*Anime\-Listeneintrag.+?}}", RegexOptions.Singleline); |
|||
Regex commentRegex = new Regex(@"<!--.+?-->", RegexOptions.Singleline); |
|||
foreach (String letter in alphabeticLists.Keys.OrderBy(key => key)) |
|||
{ |
|||
System.Console.WriteLine("Liste " + letter); |
|||
String content = commentRegex.Replace(alphabeticLists[letter].Content, ""); |
|||
foreach (Match match in templateRegex.Matches(content)) |
|||
{ |
|||
Dictionary<String, String> entry = ParseTemplate(match.Value); |
|||
AnimeItem anime = AnimeItem.Parse(entry, letter); |
|||
if (anime != null) |
|||
{ |
|||
animeList.Add(anime); |
|||
//System.Console.WriteLine(" " + anime); |
|||
} |
|||
else System.Console.Error.WriteLine("Nicht auswertbarer Eintrag: " + match.Value); |
|||
} |
|||
} |
|||
#endregion |
|||
if (matchStudiosFilename != null) |
|||
// Jahreslisten ergänzen |
|||
{ |
|||
int maxYear = animeList.Select(anime => anime.Year).Max(); |
|||
#region Studionamen ersetzen |
|||
if (maxYear > DateTime.Now.Year + 1) |
|||
Console.WriteLine("\nErsetzungen:"); |
|||
Dictionary<Regex, string> replacers = new Dictionary<Regex, string>(); |
|||
System.Console.Write("Das höchste gefundene Jahr ist " + maxYear + ". Jahreslisten bis dahin erstellen [j/n]: "); |
|||
foreach (string line in File.ReadAllLines(matchStudiosFilename, Encoding.UTF8)) |
|||
if (System.Console.ReadLine().ToLowerInvariant() != "j") return; |
|||
{ |
|||
string[] items = line.Split('\t'); |
|||
for (int year = 2000; year <= maxYear; year = year + 2) |
|||
if (items.Length >= 2) replacers.Add(new Regex(@"(\|\s*s[0-9]+\s*=\s*\[*)" + Regex.Escape(items[0]) + @"(\s*[\|}\]])", RegexOptions.Singleline | RegexOptions.CultureInvariant), items[1]); |
|||
{ |
|||
Console.WriteLine(" " + items[0] + " -> " + items[1]); |
|||
} |
|||
Console.Write("Fortfahren [j/n]: "); |
|||
if (Console.ReadLine().ToLowerInvariant() == "j") |
|||
for (int yearIdx = 0; yearIdx < yearList.Count / 2; yearIdx++) |
|||
{ |
|||
foreach (string letter in alphabeticLists.Keys.OrderBy(key => key)) |
|||
int startYear = yearList[yearIdx * 2]; |
|||
int endYear = yearList[yearIdx * 2 + 1]; |
|||
TextWriter yearWriter = new StringWriter(); |
|||
if (BuildAnimeYearList(animeList, startYear, endYear, yearWriter)) |
|||
WriteContent("Liste der Anime-Titel (" + startYear + "–" + endYear + ")", yearWriter.ToString()); |
|||
} |
|||
TextWriter studioWriter = new StringWriter(); |
|||
if (BuildAnimeStudioList(animeList, studioWriter)) WriteContent("Vorlage:Animestudio Werksliste", studioWriter.ToString()); |
|||
TextWriter maintainenceWriter = new StringWriter(); |
|||
if (BuildMaintainenceInfo(animeList, maintainenceWriter)) WriteContent("Benutzer:Mps/Test", maintainenceWriter.ToString()); |
|||
} |
|||
System.Console.WriteLine("Fertig."); |
|||
} |
|||
catch (MediawikiException ex) |
|||
{ |
{ |
||
string content = alphabeticLists[letter].Content; |
|||
System.Console.Error.WriteLine("Mediawiki-Fehler: " + ex.Message); |
|||
foreach (Regex replace in replacers.Keys) content = replace.Replace(content, "$1" + replacers[replace] + "$2"); |
|||
WriteContent("Liste der Anime-Titel/" + letter, content, alphabeticLists[letter].Timestamp); |
|||
} |
} |
||
} |
|||
#endregion |
|||
} |
|||
else |
|||
{ |
|||
#region Eingabelisten parsen |
|||
List<AnimeItem> animeList = new List<AnimeItem>(); |
|||
Regex templateRegex = new Regex(@"\{\{\s*Anime\-Listeneintrag.+?}}", RegexOptions.Singleline); |
|||
Regex commentRegex = new Regex(@"<!--.+?-->", RegexOptions.Singleline); |
|||
foreach (string letter in alphabeticLists.Keys.OrderBy(key => key)) |
|||
{ |
|||
Console.WriteLine("Liste " + letter); |
|||
string content = commentRegex.Replace(alphabeticLists[letter].Content, ""); |
|||
foreach (Match match in templateRegex.Matches(content)) |
|||
{ |
{ |
||
Dictionary<string, string> entry = ParseTemplate(match.Value); |
|||
AnimeItem anime = AnimeItem.Parse(entry, letter); |
|||
if (anime != null) |
|||
{ |
|||
animeList.Add(anime); |
|||
//Console.WriteLine(" " + anime); |
|||
} |
|||
else Console.Error.WriteLine("Nicht auswertbarer Eintrag: " + match.Value); |
|||
} |
} |
||
} |
|||
#endregion |
|||
// Jahreslisten ergänzen |
|||
int maxYear = animeList.Select(anime => anime.Year).Max(); |
|||
if (maxYear > DateTime.Now.Year + 1) |
|||
{ |
|||
Console.Write("Das höchste gefundene Jahr ist " + maxYear + ". Jahreslisten bis dahin erstellen [j/n]: "); |
|||
if (Console.ReadLine().ToLowerInvariant() != "j") return; |
|||
} |
|||
for (int year = 2000; year <= maxYear; year = year + 2) |
|||
{ |
|||
yearList.Add(year); |
|||
yearList.Add(year + 1); |
|||
} |
|||
for (int yearIdx = 0; yearIdx < yearList.Count / 2; yearIdx++) |
|||
{ |
|||
int startYear = yearList[yearIdx * 2]; |
|||
int endYear = yearList[yearIdx * 2 + 1]; |
|||
TextWriter yearWriter = new StringWriter(); |
|||
if (BuildAnimeYearList(animeList, startYear, endYear, yearWriter)) |
|||
WriteContent("Liste der Anime-Titel (" + startYear + "–" + endYear + ")", yearWriter.ToString()); |
|||
} |
|||
TextWriter studioWriter = new StringWriter(); |
|||
if (BuildAnimeStudioList(animeList, studioWriter)) WriteContent("Vorlage:Animestudio Werksliste", studioWriter.ToString()); |
|||
TextWriter maintainenceWriter = new StringWriter(); |
|||
if (BuildMaintainenceInfo(animeList, maintainenceWriter)) WriteContent("Benutzer:Mps/Test", maintainenceWriter.ToString()); |
|||
} |
|||
Console.WriteLine("Fertig."); |
|||
} |
|||
catch (MediawikiException ex) |
|||
{ |
|||
Console.Error.WriteLine("Mediawiki-Fehler: " + ex.Message); |
|||
} |
|||
finally |
|||
{ |
|||
//wiki.Logout(); |
|||
} |
|||
#if (DEBUG) |
#if (DEBUG) |
||
Console.ReadLine(); |
|||
#endif |
#endif |
||
} |
|||
} |
} |
||
} |
|||
} |
} |
||
</source> |
</source> |
Version vom 18. August 2016, 22:18 Uhr
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
namespace AnimeListenUpdater
{
#region Exceptions
class BusinessException : Exception
{
public BusinessException(string message)
: base(message)
{ }
}
class MediawikiException : BusinessException
{
public string Code { get; }
public MediawikiException(string message)
: base(message)
{ }
public MediawikiException(string code, string info)
: base("Mediawiki error \"" + code + ": " + info + "\"")
{
Code = code;
}
}
class MediawikiLagException : MediawikiException
{
public int LagTime { get; private set; }
public MediawikiLagException(string code, string info)
: base("Mediawiki error \"" + code + ": " + info + "\"")
{
LagTime = GetLagTime(info);
}
public static int GetLagTime(string info)
{
int lagtime = -1;
Match match = Regex.Match(info, "Waiting for [^ ]*: ([0-9.-]+) seconds lagged");
if (match.Success) int.TryParse(match.Groups[1].Value, out lagtime);
return lagtime;
}
}
#endregion
public class Mediawiki
{
#region Netzwerk- und Mediawiki-Funktionen
static CookieContainer cookieContainer = new CookieContainer();
static readonly string userAgentString = GetUserAgentString();
static string GetUserAgentString()
{
AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();
// Programmname und -version
string userAgent = assemblyName.Name + "/" + assemblyName.Version.Major + "." + assemblyName.Version.Minor;
// Umgebungsinformationen (OS, Laufzeitumgebung)
userAgent += " (" + Environment.OSVersion.VersionString + "; .NET CLR " + Environment.Version + ")";
return userAgent;
}
private static string EncodeQuery(string[,] query)
{
string result = "";
for (int i = 0; i < query.GetLength(0); i++)
{
result += "&" + query[i, 0];
string value = query[i, 1];
if (value != null) result += "=" + Uri.EscapeDataString(value);
}
return result;
}
private static string MultipartQuery(string[,] postData, string boundaryIdentifier)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < postData.GetLength(0); i++)
{
sb.Append("--");
sb.Append(boundaryIdentifier);
sb.Append("\r\nContent-Disposition: form-data; name=\"");
sb.Append(postData[i, 0]);
sb.Append("\"\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n");
sb.Append(postData[i, 1]);
sb.Append("\r\n");
}
return sb.ToString();
}
static HttpWebRequest GenerateHttpWebRequest(string language, string[,] query)
{
string url = "https://";
if (language == "wikidata") url += "www.wikidata.org"; else url += language + ".wikipedia.org";
url += "/w/api.php?format=xml";
url += EncodeQuery(query);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.UserAgent = userAgentString;
request.CookieContainer = cookieContainer;
request.AutomaticDecompression = DecompressionMethods.GZip;
request.KeepAlive = true;
return request;
}
public static HttpWebResponse HttpGet(string language, string[,] query)
{
HttpWebRequest request = GenerateHttpWebRequest(language, query);
return (HttpWebResponse)request.GetResponse();
}
public static HttpWebResponse HttpPost(string language, string[,] query, string[,] postData)
{
HttpWebRequest request = GenerateHttpWebRequest(language, query);
request.Method = "POST";
if (postData != null)
{
string boundary = Guid.NewGuid().ToString();
byte[] postContent = new UTF8Encoding(false).GetBytes(MultipartQuery(postData, boundary));
request.ContentType = "multipart/form-data; boundary=" + boundary;
using (Stream stream = request.GetRequestStream())
{
stream.Write(postContent, 0, postContent.Length);
}
}
return (HttpWebResponse)request.GetResponse();
}
public static void TraverseHttpWebResponse(HttpWebResponse response, Action<XmlReader> onXmlNode)
{
using (response)
using (Stream stream = response.GetResponseStream())
using (XmlReader reader = XmlReader.Create(stream))
{
if (!reader.ReadToFollowing("api")) throw new MediawikiException("Malformed response");
while (reader.Read())
{
CheckForError(reader);
onXmlNode(reader);
}
}
}
public static string CalcMd5Hash(byte[] data)
{
System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] md5Hash = md5.ComputeHash(data);
return BitConverter.ToString(md5Hash).Replace("-", "").ToLowerInvariant();
}
public static void CheckForError(XmlReader reader)
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.LocalName == "warnings") Debug.WriteLine(" warning: " + reader.ReadInnerXml());
else if (reader.LocalName == "error")
{
string code = reader.GetAttribute("code");
string info = reader.GetAttribute("info");
Debug.WriteLine(" error \"" + code + "\": " + info);
if (code == "maxlag")
throw new MediawikiLagException(code, info);
else
throw new MediawikiException(code, info);
}
}
}
public enum TokenType { Csrf, Watch, Patrol, Rollback, UserRights, Login, CreateAccount }
public static string GetToken(string lang, TokenType tokenType)
{
string tokenName = tokenType.ToString().ToLowerInvariant();
string[,] paramList = new[,] {
{ "action", "query" },
{ "meta", "tokens" },
{ "type", tokenName }
};
string token = null;
TraverseHttpWebResponse(HttpPost(lang, paramList, null), reader =>
{
if ((reader.NodeType == XmlNodeType.Element) && (reader.LocalName == "tokens"))
{
token = reader.GetAttribute(tokenName + "token");
}
});
return token;
}
public static void Login(string lang, NetworkCredential credentials)
{
string[,] paramList = new[,]
{
{ "action", "login" },
{ "lgname", credentials.UserName },
{ "lgpassword", credentials.Password },
{ "lgtoken", GetToken(lang, TokenType.Login) }
};
TraverseHttpWebResponse(HttpPost(lang, paramList, null), reader =>
{
if ((reader.NodeType == XmlNodeType.Element) && (reader.LocalName == "login"))
{
string result = reader.GetAttribute("result");
if (result != "Success")
{
if (result == "Throttled") result += $" (Please wait {reader.GetAttribute("wait")}s)";
throw new MediawikiException(result);
}
}
});
}
public static void Logout(string lang)
{
using (HttpWebResponse response = HttpGet(lang, new[,] { { "action", "logout" } })) { }
}
#endregion
}
public class Program
{
static Mediawiki wiki;
static string editToken = null;
public class AnimeItem
{
public string List;
public int Level;
public int Year;
public string Type;
public string EpisodeCount;
public string Title;
public string Link;
public string JapaneseTitle;
public string JapaneseTranscription;
public List<string> AlternativeTitles;
public List<string> Studios;
public static AnimeItem Parse(Dictionary<string, string> parsedTemplate, string list)
{
AnimeItem result = new AnimeItem();
try
{
result.List = list;
result.Level = Int32.Parse(parsedTemplate["1"]);
result.Year = Int32.Parse(parsedTemplate["2"]);
result.Type = parsedTemplate["3"].Trim();
result.EpisodeCount = parsedTemplate["4"].Trim();
result.Title = parsedTemplate["5"].Trim();
if (parsedTemplate.TryGetValue("link", out result.Link)) result.Link = result.Link.Trim();
if (parsedTemplate.ContainsKey("j")) result.JapaneseTitle = parsedTemplate["j"].Trim(); else result.JapaneseTitle = "";
if (parsedTemplate.ContainsKey("jt")) result.JapaneseTranscription = parsedTemplate["jt"].Trim(); else result.JapaneseTranscription = "";
result.AlternativeTitles = new List<string>();
result.Studios = new List<string>();
for (int i = 1; i <= 10; i++)
{
string paramName = "a" + i;
if (parsedTemplate.ContainsKey(paramName)) result.AlternativeTitles.Add(parsedTemplate[paramName].Trim());
paramName = "s" + i;
if (parsedTemplate.ContainsKey(paramName)) result.Studios.Add(parsedTemplate[paramName].Trim());
}
return result;
}
catch (KeyNotFoundException) { return null; }
catch (FormatException) { return null; }
}
public string GetWikilink()
{
string result = "[[";
if (!string.IsNullOrEmpty(Link)) result += Link + "|";
result += Title + "]]";
return result;
}
public static string GetWikiArticle(string wikilink)
{
if (string.IsNullOrEmpty(wikilink)) return "";
else
{
// entferne Wikilinkklammern und splitte an der ersten Pipe
string link = wikilink.TrimStart('[').TrimEnd(']').Split(new char[] { '|' }, 2)[0];
// erstes Element (Zielartikel) mit Großbuchstaben beginnen lassen und zurückgeben
link = Char.ToUpperInvariant(link[0]) + link.Substring(1);
return link;
}
}
public string GetYearListEntry()
{
StringBuilder sb = new StringBuilder();
sb.Append("{{Anime-Listeneintrag|");
sb.Append(0);
sb.Append("|");
sb.Append(Year);
sb.Append("|");
sb.Append(Type);
sb.Append("|");
sb.Append(EpisodeCount);
sb.Append("|");
sb.Append(Title);
if (!string.IsNullOrEmpty(Link))
{
sb.Append("|link=");
sb.Append(Link);
}
if (!string.IsNullOrEmpty(JapaneseTitle))
{
sb.Append("|j=");
sb.Append(JapaneseTitle);
}
if (!string.IsNullOrEmpty(JapaneseTranscription))
{
sb.Append("|jt=");
sb.Append(JapaneseTranscription);
}
for (int aIdx = 0; aIdx < AlternativeTitles.Count; aIdx++)
{
sb.Append("|a");
sb.Append(aIdx + 1);
sb.Append("=");
sb.Append(AlternativeTitles[aIdx]);
}
for (int sIdx = 0; sIdx < Studios.Count; sIdx++)
{
sb.Append("|s");
sb.Append(sIdx + 1);
sb.Append("=");
sb.Append(Studios[sIdx]);
}
sb.Append("|mark=year}}");
return sb.ToString();
}
public override string ToString()
{
return Title;
}
}
#region Konfiguration
static UnicodeCategory[] validCharTypes = new UnicodeCategory[] {
UnicodeCategory.UppercaseLetter, UnicodeCategory.LowercaseLetter, UnicodeCategory.TitlecaseLetter, UnicodeCategory.ModifierLetter, UnicodeCategory.OtherLetter,
UnicodeCategory.DecimalDigitNumber, UnicodeCategory.LetterNumber, UnicodeCategory.OtherNumber, UnicodeCategory.SpaceSeparator };
static string[] alphaList = new string[] { "0–9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
static List<int> yearList = new List<int> { 1900, 1945, 1946, 1962, 1963, 1969, 1970, 1979, 1980, 1984, 1985, 1989, 1990, 1994, 1995, 1997, 1998, 1999 }; // ab 2000 wird automatisch generiert
static Dictionary<string, string> animeTypes = new Dictionary<string, string> {
{ "F", "Film" },
{ "S", "Fernsehserie" },
{ "W", "Web-Anime" },
{ "O", "OVA" },
{ "SP", "Special" },
{ "K", "Kurzfilm" }
};
#endregion
static Dictionary<string, string> ParseTemplate(string template)
{
Dictionary<string, string> result = new Dictionary<string, string>();
int startPos = template.IndexOf("{{");
int lastPos = template.LastIndexOf("}}");
if (startPos >= 0 && lastPos >= 0)
{
string[] split = template.Substring(startPos + 2, lastPos - startPos - 2).Split('|');
List<string> parameters = new List<string>();
// Wikilinks wieder zusammenführen, d.h. "[[A", "B]]" zu "[[A|B]]".
int doubleBracketCount = 0;
for (int i = 0; i < split.Length; i++)
{
if (doubleBracketCount == 0) parameters.Add(split[i]); else parameters[parameters.Count - 1] += "|" + split[i];
int curIdx = 0;
while (curIdx >= 0)
{
curIdx = split[i].IndexOf("[[", curIdx);
if (curIdx >= 0)
{
doubleBracketCount++;
curIdx += 2;
}
}
curIdx = 0;
while (curIdx >= 0)
{
curIdx = split[i].IndexOf("]]", curIdx);
if (curIdx >= 0)
{
doubleBracketCount--;
curIdx += 2;
}
}
}
for (int paramIdx = 0; paramIdx < parameters.Count; paramIdx++)
{
string[] keyValue = parameters[paramIdx].Split(new char[] { '=' }, 2);
try
{
if (keyValue.Length == 2) result.Add(keyValue[0], keyValue[1]); else result.Add(paramIdx.ToString(), parameters[paramIdx]);
}
catch (ArgumentException)
{
Console.Error.WriteLine("Doppelte Parameter: " + template);
}
}
}
return result;
}
// zur Sortierung alle Zeichen außer Buchstaben, Zahlen und Leerzeichen entfernen und Buchstaben in Kleinschreibung umwandeln
static string RemoveDiacriticsAndSpecialChars(string text)
{
if (text.StartsWith("The ") || text.StartsWith("Der ") || text.StartsWith("Die ") || text.StartsWith("Das ")) text = text.Substring(4);
return string.Concat(text.Normalize(NormalizationForm.FormKD).Where(ch => validCharTypes.Contains(CharUnicodeInfo.GetUnicodeCategory(ch)))).Normalize(NormalizationForm.FormKC).ToLowerInvariant();
}
static void WriteContent(string pagetitle, string content, string basetimestamp = null)
{
if (editToken == null) editToken = Mediawiki.GetToken("de", Mediawiki.TokenType.Csrf);
bool badToken;
do
{
badToken = false;
try
{
Console.Write("Schreibe " + pagetitle);
string md5Hash = Mediawiki.CalcMd5Hash(new UTF8Encoding(false).GetBytes(content));
var post = new string[3 + (basetimestamp != null ? 1 : 0), 2];
post[0, 0] = "token";
post[0, 1] = editToken;
post[1, 0] = "text";
post[1, 1] = content;
post[2, 0] = "md5";
post[2, 1] = md5Hash;
if (basetimestamp != null)
{
post[3, 0] = "basetimestamp";
post[3, 1] = basetimestamp;
}
using (HttpWebResponse response = Mediawiki.HttpPost("de", new[,] {
{ "assert", "user" },
{ "action", "edit" },
{ "title", pagetitle },
{ "notminor", null },
{ "summary", "Listen-Aktualisierung per [[Benutzer:Mps/AnimeListenUpdater.cs]]" } },
post))
using (Stream stream = response.GetResponseStream())
using (XmlReader reader = XmlReader.Create(stream))
{
while (reader.Read()) Mediawiki.CheckForError(reader);
}
Console.WriteLine(".");
}
catch (MediawikiException mwe)
{
Console.Error.WriteLine("\n" + pagetitle + ": " + mwe.Message);
if (mwe.Code == "badtoken")
{
// Die API wirft manchmal "badtoken", daher einfach Edit nochmal probieren.
Console.Error.WriteLine(" Neuer Versuch.");
badToken = true;
}
}
} while (badToken);
}
static bool BuildAnimeYearList(List<AnimeItem> animeList, int startYear, int endYear, TextWriter writer)
{
writer.WriteLine("__ABSCHNITTE_NICHT_BEARBEITEN__ __KEIN_INHALTSVERZEICHNIS__");
writer.WriteLine("Dies ist eine '''chronologisch sortierte Liste der Anime-Titel''' von " + startYear + " bis " + endYear + ".");
writer.WriteLine("<!--");
writer.WriteLine("##########");
writer.WriteLine("Bitte editiere diese Liste _nicht_, da sie von einem Bot automatisch generiert wird, der durchgeführte Änderungen überschreibt. Stattdessen können und sollten die alphabetisch sortierten Listen der Liste der Anime-Titel http://de.wikipedia.org/wiki/Liste_der_Anime-Titel bearbeitet werden, aus denen diese Liste generiert wird.");
writer.WriteLine("##########");
writer.WriteLine("-->");
writer.WriteLine("{{Navigationsleiste Liste der Anime-Titel}}");
writer.WriteLine();
bool hasAtLeastOneYear = false;
for (int year = startYear; year <= endYear; year++)
{
IEnumerable<AnimeItem> animesInYear = from anime in animeList
where anime.Year == year
orderby RemoveDiacriticsAndSpecialChars(anime.Title)
select anime;
if (animesInYear.Any())
{
hasAtLeastOneYear = true;
writer.WriteLine("== " + year + " ==");
writer.WriteLine("{{Anime-Listenkopf|");
foreach (AnimeItem anime in animesInYear)
{
writer.WriteLine(anime.GetYearListEntry());
}
writer.WriteLine("|mark=year}}");
writer.WriteLine();
}
}
writer.WriteLine("[[Kategorie:Liste (Anime)|#" + startYear + "]]");
return hasAtLeastOneYear;
}
static bool BuildAnimeStudioList(List<AnimeItem> animeList, TextWriter writer)
{
IEnumerable<string> linkedStudios = animeList.SelectMany(anime => anime.Studios) // Studiolisten zusammenführen
.Where(studio => studio.StartsWith("[[")) // nach verlinkten Studios filtern
.Select(studio => AnimeItem.GetWikiArticle(studio)) // Linkklammern entfernen, nach Pipe separieren und nur erstes Element (Linkziel) zurückgeben
.Distinct() // doppelte Einträge entfernen
.OrderBy(name => RemoveDiacriticsAndSpecialChars(name)); // sortieren
foreach (string curStudio in linkedStudios)
{
writer.WriteLine("{{#ifeq: {{{1}}}|" + curStudio + "|");
Dictionary<string, List<AnimeItem>> animesOfStudio = animeList
.Where(anime => anime.Studios.Any(studio => AnimeItem.GetWikiArticle(studio) == curStudio)) // nach Anime die vom aktuellen Studio gefertigt wurden filtern
.OrderBy(anime => anime.Year + RemoveDiacriticsAndSpecialChars(anime.Title)) // nach Jahr und Titel sortieren
.GroupBy(anime => anime.Type) // nach Typ gruppieren
.ToDictionary(group => group.Key, group => group.ToList()); // in assoziative Liste überführen
// Generierungsregeln:
// ab 10 Einträge pro Typ: eigene Gruppe (Reihenfolge: Fernsehserien, Filme, Original Video Animations, Specials, Kurzfilme, Weitere Anime-Produktionen)
// ab 10 Einträge pro Gruppe: zwei Spalten, wobei bei ungerader Anzahl die erste Spalte länger ist
bool hasCaptions = false;
foreach (string animeType in animeTypes.Keys)
{
#region wenn ein Studio mehr als 10 Animes eines Typs produziert hat, diese separat aufführen
if (animesOfStudio.ContainsKey(animeType) && animesOfStudio[animeType].Count >= 10)
{
hasCaptions = true;
switch (animeType)
{
case "S":
writer.WriteLine("=== Fernsehserien ===");
break;
case "F":
writer.WriteLine("=== Filme ===");
break;
case "W":
writer.WriteLine("=== Web-Animes ===");
break;
case "O":
writer.WriteLine("=== Original Video Animations ===");
break;
case "SP":
writer.WriteLine("=== Specials ===");
break;
case "K":
writer.WriteLine("=== Kurzfilme ===");
break;
}
writer.WriteLine("{{(!}} class=\"toptextcells\"");
writer.WriteLine("{{!}}");
int newColIdx = (int)Math.Ceiling(animesOfStudio[animeType].Count / 2.0);
int animeIdx = 0;
foreach (AnimeItem anime in animesOfStudio[animeType])
{
writer.WriteLine("* " + anime.Year + ": ''" + anime.GetWikilink() + "''");
animeIdx++;
if (animeIdx == newColIdx) writer.WriteLine("{{!}}");
}
writer.WriteLine("{{!)}}");
animesOfStudio.Remove(animeType);
}
#endregion
}
IEnumerable<AnimeItem> otherAnimes = animesOfStudio
.SelectMany(kvp => kvp.Value)
.OrderBy(anime => anime.Year + RemoveDiacriticsAndSpecialChars(anime.Title));
if (otherAnimes.Any())
{
if (hasCaptions) writer.WriteLine("=== Weitere Anime-Produktionen ===");
int newColIdx = (int)Math.Ceiling(otherAnimes.Count() / 2.0);
if (hasCaptions || newColIdx >= 5)
{
writer.WriteLine("{{(!}} class=\"toptextcells\"");
writer.WriteLine("{{!}}");
}
else newColIdx = -1;
int animeIdx = 0;
foreach (AnimeItem anime in otherAnimes)
{
string desc;
if (animeTypes.ContainsKey(anime.Type)) desc = " (" + animeTypes[anime.Type] + ")"; else desc = "";
writer.WriteLine("* " + anime.Year + ": ''" + anime.GetWikilink() + "''" + desc);
animeIdx++;
if (animeIdx == newColIdx) writer.WriteLine("{{!}}");
}
if (hasCaptions || newColIdx >= 5) writer.WriteLine("{{!)}}");
}
writer.Write("}}");
}
writer.Write("<div class=\"noprint\" style=\"padding-left:1em;\"><small>''Diese Liste wird aus den Einträgen der [[Liste der Anime-Titel]] generiert. Einträge können und sollten dort ergänzt werden.</small></div>");
writer.Write("<noinclude>[[Kategorie:Vorlage:Film und Fernsehen]]</noinclude>");
return true;
}
static bool BuildMaintainenceInfo(List<AnimeItem> animeList, TextWriter writer)
{
var allStudios = animeList.SelectMany(anime => anime.Studios, (anime, studio) => new { Name = studio, List = anime.List });
var linkedStudios = allStudios
.Where(studio => studio.Name.StartsWith("[["))
.Select(studio => new { Name = AnimeItem.GetWikiArticle(studio.Name), List = studio.List })
.GroupBy(studio => studio.Name)
.Select(studio => new { Name = studio.Key, Count = studio.Count(), Lists = studio.Select(list => list.List).Distinct().OrderBy(list => list) })
.OrderBy(studio => studio.Name);
var unlinkedStudios = allStudios
.Where(studio => !studio.Name.StartsWith("[["))
.Select(studio => new { Name = studio.Name, List = studio.List })
.GroupBy(studio => studio.Name)
.Select(studio => new { Name = studio.Key, Count = studio.Count(), Lists = studio.Select(list => list.List).Distinct().OrderBy(list => list) });
writer.WriteLine("== Nur teilweise verlinkte Studios ==");
writer.WriteLine("{| class=\"wikitable sortable\"");
writer.WriteLine("! Studio || Auftreten || Listen");
foreach (var linkedStudio in linkedStudios)
{
var match = unlinkedStudios.FirstOrDefault(unlinkedStudio => unlinkedStudio.Name == linkedStudio.Name);
if (match != null)
{
writer.WriteLine("|-\n| {0} || {1} || {2}", linkedStudio.Name, match.Count, string.Join(", ", match.Lists.Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]")));
}
}
writer.WriteLine("|}\n");
writer.WriteLine("== Potentiell verlinkbare Studios ==");
writer.WriteLine("{| class=\"wikitable sortable\"");
writer.WriteLine("! Studio || Anzahl || Listen");
foreach (var unlinkedStudio in unlinkedStudios)
{
if (!string.IsNullOrEmpty(unlinkedStudio.Name) && unlinkedStudio.Count > 5 && linkedStudios.All(linkedStudio => linkedStudio.Name != unlinkedStudio.Name))
{
writer.WriteLine("|-\n| {0} || {1} || {2}", unlinkedStudio.Name, unlinkedStudio.Count, string.Join(", ", unlinkedStudio.Lists.Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]")));
}
}
writer.WriteLine("|}\n");
Dictionary<string, List<string>> combinedStudioList = allStudios
.Where(studio => !string.IsNullOrEmpty(studio.Name))
.Select(studio => new { Name = AnimeItem.GetWikiArticle(studio.Name), List = studio.List })
.GroupBy(studio => studio.Name)
.OrderBy(studio => studio.Key)
.ToDictionary(group => group.Key, group => group.Select(studio => studio.List).Distinct().ToList());
writer.WriteLine("== Ähnliche Studionamen ==");
writer.WriteLine("{| class=\"wikitable sortable\"");
writer.WriteLine("! Studio || Ähnliche Namen || Listen");
string[] allStudiosArray = combinedStudioList.Keys.ToArray();
for (int i = 0; i < allStudiosArray.Length; i++)
{
string targetStudio = allStudiosArray[i];
Dictionary<string, int> similarStudios = new Dictionary<string, int>();
for (int j = 0/*i + 1*/; j < allStudiosArray.Length; j++)
{
if (i == j) continue;
string otherStudio = allStudiosArray[j];
int dist = LevenshteinDistance(RemoveDiacriticsAndSpecialChars(targetStudio), RemoveDiacriticsAndSpecialChars(otherStudio));
if (dist <= 2)
{
similarStudios.Add(otherStudio, dist);
}
}
if (similarStudios.Count > 0)
{
writer.Write("|-\n| rowspan=\"{0}\" | {1} |", similarStudios.Count, targetStudio);
foreach (string similarName in similarStudios.OrderBy(item => item.Value).Select(item => item.Key))
{
writer.WriteLine("| {0} || {1}\r\n|-", similarName,
string.Join(", ", combinedStudioList[similarName].Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]")));
}
}
}
writer.WriteLine("|}");
return true;
}
static int LevenshteinDistance(string source, string target)
{
if (string.IsNullOrEmpty(source) && string.IsNullOrEmpty(target)) return 0;
if (string.IsNullOrEmpty(source)) return target.Length;
if (string.IsNullOrEmpty(target)) return source.Length;
int[,] d = new int[source.Length + 1, target.Length + 1];
for (int i = 1; i <= source.Length; i++) d[i, 0] = i;
for (int j = 1; j <= target.Length; j++) d[0, j] = j;
for (int j = 1; j <= target.Length; j++)
{
for (int i = 1; i <= source.Length; i++)
{
if (source[i - 1] == target[j - 1])
d[i, j] = d[i - 1, j - 1];
else
d[i, j] = new int[] {
d[i - 1, j] + 1, // Löschung
d[i, j - 1] + 1, // Einfügung
d[i - 1, j - 1] + 1 // Ersetzung
}.Min();
}
}
return d[source.Length, target.Length];
}
struct ContentAndTimestamp
{
public string Content;
public string Timestamp;
}
private static NetworkCredential GetCredentials()
{
Console.Write("Benutzer: ");
string user = Console.ReadLine();
Console.Write("Passwort: ");
string password = string.Empty;
// Sternchen zeigen, statt eingetipptem Passwort
while (true)
{
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
if (keyInfo.Key == ConsoleKey.Enter)
{
Console.WriteLine();
break;
}
else if (keyInfo.Key == ConsoleKey.Backspace)
{
if (password.Length > 0)
{
password = password.Remove(password.Length - 1);
// bei Backspace: Cursor eine Position zurück, mit Leerzeichen überschreiben, wieder eine Position zurück
Console.Write(keyInfo.KeyChar + " " + keyInfo.KeyChar);
}
}
else
{
password += keyInfo.KeyChar;
Console.Write("*");
}
}
Console.WriteLine();
return new NetworkCredential(user, password);
}
static void Main(string[] args)
{
string matchStudiosFilename = null;
NetworkCredential credentials = null;
#region Kommandozeilenparameter auswerten
string user = null, password = null;
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "-?")
{
Console.WriteLine(Assembly.GetExecutingAssembly().GetName() + " [Parameter]");
Console.WriteLine();
Console.WriteLine("-user Benutzername Benutzername angeben");
Console.WriteLine("-pass Passwort Passwort angeben");
Console.WriteLine("-matchstudios Datei Studionamen in den Jahreslisten angleichen.");
Console.WriteLine(" Eingabe ist eine UTF8-kodierte Datei die zeilenweise die Ersetzungen angibt im Format: Ist<TAB>Soll");
return;
}
else
if (i + 1 < args.Length)
{
switch (args[i])
{
case "-user":
user = args[i + 1];
i++;
break;
case "-pass":
password = args[i + 1];
i++;
break;
case "-matchstudios":
matchStudiosFilename = args[i + 1];
if (!File.Exists(matchStudiosFilename))
{
Console.WriteLine("Datei nicht gefunden!");
return;
}
break;
}
}
}
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password)) { credentials = new NetworkCredential(user, password); }
#endregion
wiki = new Mediawiki();
try
{
if (credentials == null) { credentials = GetCredentials(); }
Mediawiki.Login("de", credentials);
#region Eingabelisten lesen
Console.WriteLine("Alphabetische Listen lesen:");
Console.Write(" ");
Dictionary<string, ContentAndTimestamp> alphabeticLists = new Dictionary<string, ContentAndTimestamp>();
Parallel.ForEach(alphaList, letter =>
{
Mediawiki.TraverseHttpWebResponse(Mediawiki.HttpGet("de", new[,] {
{ "action", "query" },
{ "prop", "revisions" },
{ "titles", "Liste der Anime-Titel/" + letter },
{ "rvprop", "content|timestamp" } }),
delegate (XmlReader reader)
{
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "rev"))
{
string curTimestamp = reader.GetAttribute("timestamp");
string content = reader.ReadString();
lock (alphabeticLists)
{
alphabeticLists.Add(letter, new ContentAndTimestamp() { Content = content, Timestamp = curTimestamp });
Console.Write(letter);
}
}
});
});
Console.WriteLine();
#endregion
if (matchStudiosFilename != null)
{
#region Studionamen ersetzen
Console.WriteLine("\nErsetzungen:");
Dictionary<Regex, string> replacers = new Dictionary<Regex, string>();
foreach (string line in File.ReadAllLines(matchStudiosFilename, Encoding.UTF8))
{
string[] items = line.Split('\t');
if (items.Length >= 2) replacers.Add(new Regex(@"(\|\s*s[0-9]+\s*=\s*\[*)" + Regex.Escape(items[0]) + @"(\s*[\|}\]])", RegexOptions.Singleline | RegexOptions.CultureInvariant), items[1]);
Console.WriteLine(" " + items[0] + " -> " + items[1]);
}
Console.Write("Fortfahren [j/n]: ");
if (Console.ReadLine().ToLowerInvariant() == "j")
{
foreach (string letter in alphabeticLists.Keys.OrderBy(key => key))
{
string content = alphabeticLists[letter].Content;
foreach (Regex replace in replacers.Keys) content = replace.Replace(content, "$1" + replacers[replace] + "$2");
WriteContent("Liste der Anime-Titel/" + letter, content, alphabeticLists[letter].Timestamp);
}
}
#endregion
}
else
{
#region Eingabelisten parsen
List<AnimeItem> animeList = new List<AnimeItem>();
Regex templateRegex = new Regex(@"\{\{\s*Anime\-Listeneintrag.+?}}", RegexOptions.Singleline);
Regex commentRegex = new Regex(@"<!--.+?-->", RegexOptions.Singleline);
foreach (string letter in alphabeticLists.Keys.OrderBy(key => key))
{
Console.WriteLine("Liste " + letter);
string content = commentRegex.Replace(alphabeticLists[letter].Content, "");
foreach (Match match in templateRegex.Matches(content))
{
Dictionary<string, string> entry = ParseTemplate(match.Value);
AnimeItem anime = AnimeItem.Parse(entry, letter);
if (anime != null)
{
animeList.Add(anime);
//Console.WriteLine(" " + anime);
}
else Console.Error.WriteLine("Nicht auswertbarer Eintrag: " + match.Value);
}
}
#endregion
// Jahreslisten ergänzen
int maxYear = animeList.Select(anime => anime.Year).Max();
if (maxYear > DateTime.Now.Year + 1)
{
Console.Write("Das höchste gefundene Jahr ist " + maxYear + ". Jahreslisten bis dahin erstellen [j/n]: ");
if (Console.ReadLine().ToLowerInvariant() != "j") return;
}
for (int year = 2000; year <= maxYear; year = year + 2)
{
yearList.Add(year);
yearList.Add(year + 1);
}
for (int yearIdx = 0; yearIdx < yearList.Count / 2; yearIdx++)
{
int startYear = yearList[yearIdx * 2];
int endYear = yearList[yearIdx * 2 + 1];
TextWriter yearWriter = new StringWriter();
if (BuildAnimeYearList(animeList, startYear, endYear, yearWriter))
WriteContent("Liste der Anime-Titel (" + startYear + "–" + endYear + ")", yearWriter.ToString());
}
TextWriter studioWriter = new StringWriter();
if (BuildAnimeStudioList(animeList, studioWriter)) WriteContent("Vorlage:Animestudio Werksliste", studioWriter.ToString());
TextWriter maintainenceWriter = new StringWriter();
if (BuildMaintainenceInfo(animeList, maintainenceWriter)) WriteContent("Benutzer:Mps/Test", maintainenceWriter.ToString());
}
Console.WriteLine("Fertig.");
}
catch (MediawikiException ex)
{
Console.Error.WriteLine("Mediawiki-Fehler: " + ex.Message);
}
finally
{
//wiki.Logout();
}
#if (DEBUG)
Console.ReadLine();
#endif
}
}
}