|
|
|
using Newtonsoft.Json;
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
using System;
|
|
|
|
using System.Collections;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.Specialized;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Net;
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
using System.Web;
|
|
|
|
|
|
|
|
namespace StardewScraper
|
|
|
|
{
|
|
|
|
class Program
|
|
|
|
{
|
|
|
|
static void Main(string[] args)
|
|
|
|
{
|
|
|
|
Dictionary<string, object> dataSet = new Dictionary<string, object>();
|
|
|
|
|
|
|
|
foreach (string category in Globals.Categories)
|
|
|
|
{
|
|
|
|
|
|
|
|
string[] itemList = { "Hops", "Grape" };
|
|
|
|
|
|
|
|
List<OrderedDictionary> Items = new List<OrderedDictionary>();
|
|
|
|
|
|
|
|
// for each item in list of items - needs to be changed for JSON support
|
|
|
|
foreach (string Item in itemList)
|
|
|
|
{
|
|
|
|
String strItem = Item;
|
|
|
|
OrderedDictionary TheItem = ItemBox(strItem, category);
|
|
|
|
Items.Add(TheItem);
|
|
|
|
}
|
|
|
|
|
|
|
|
//AllItems.Dump();
|
|
|
|
//AllJson.Dump();
|
|
|
|
dataSet[category] = Items;
|
|
|
|
}
|
|
|
|
|
|
|
|
string allJson = JsonConvert.SerializeObject(dataSet, Newtonsoft.Json.Formatting.Indented);
|
|
|
|
}
|
|
|
|
|
|
|
|
static OrderedDictionary ItemBox(string strItem, string Category)
|
|
|
|
{
|
|
|
|
// skip if the node exists in our blacklist
|
|
|
|
if (!Globals.avoid.Contains(strItem) && strItem != Globals.Categories[0])
|
|
|
|
{
|
|
|
|
// get first section of item's wiki page
|
|
|
|
// this contains the infobox
|
|
|
|
// this returns JSON, not XML
|
|
|
|
string strWiki = Wikidata(strItem, "item");
|
|
|
|
|
|
|
|
// strip out the content between the starting {{ and the first line break
|
|
|
|
Regex rgx = new Regex("Infobox.*");
|
|
|
|
string strRes = rgx.Replace(strWiki, "");
|
|
|
|
|
|
|
|
// strip out everything past the closing }}
|
|
|
|
// find index of closing }}, which is always preceded by a newline
|
|
|
|
string strRep2 = "\n}}";
|
|
|
|
int intStart = strRes.IndexOf(strRep2);
|
|
|
|
|
|
|
|
if (intStart >= 0)
|
|
|
|
{
|
|
|
|
// extra pipe in the replacement string gives us the right formatting for our object
|
|
|
|
strRes = strRes.Replace(strRes.Substring(intStart), "\n|}}");
|
|
|
|
}
|
|
|
|
|
|
|
|
// split the string by the leading | on each line
|
|
|
|
string[] strSep = { "\n|" };
|
|
|
|
string[] strSplit = strRes.Split(strSep, System.StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
|
|
|
string[] strSplit2 = { "=" };
|
|
|
|
|
|
|
|
// instantiate new item, which contains the item name and a dynamic object containing its properties
|
|
|
|
OrderedDictionary itm = new OrderedDictionary();
|
|
|
|
|
|
|
|
OrderedDictionary ordProps = new OrderedDictionary();
|
|
|
|
|
|
|
|
ordProps["name"] = strItem;
|
|
|
|
|
|
|
|
// String manipulation!!
|
|
|
|
foreach (string s in strSplit)
|
|
|
|
{
|
|
|
|
string[] strElements = s.Split(strSplit2, 2, System.StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
|
|
|
char[] strTrim = { ' ', '\n' };
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
foreach (var s2 in strElements.ToList())
|
|
|
|
{
|
|
|
|
string s3 = s2.TrimStart(strTrim);
|
|
|
|
string s4 = s3.TrimEnd(strTrim);
|
|
|
|
|
|
|
|
strElements[i] = s4;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strElements.Length > 1)
|
|
|
|
{
|
|
|
|
string propertyName = strElements[0];
|
|
|
|
dynamic propertyValue = strElements[1];
|
|
|
|
|
|
|
|
//if (propertyName == "source")
|
|
|
|
if (new[] {
|
|
|
|
"source", "as", "dr", "md", "os",
|
|
|
|
"ingredients", "tingredients", "season", "produce", "drops",
|
|
|
|
"location", "occupants", "materials", "animals",
|
|
|
|
"favorites", "family", "friends",
|
|
|
|
"buff", "seed" }.Contains(propertyName))
|
|
|
|
{
|
|
|
|
switch (propertyName)
|
|
|
|
{
|
|
|
|
case "source":
|
|
|
|
propertyName = "sources";
|
|
|
|
break;
|
|
|
|
case "as":
|
|
|
|
propertyName = "wigglies";
|
|
|
|
break;
|
|
|
|
case "dr":
|
|
|
|
propertyName = "reward";
|
|
|
|
break;
|
|
|
|
case "md":
|
|
|
|
propertyName = "drops";
|
|
|
|
break;
|
|
|
|
case "os":
|
|
|
|
propertyName = "othersrc";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<String> sourceList = new List<String>();
|
|
|
|
string s2 = s;
|
|
|
|
|
|
|
|
if (s2.Contains("[[") || s2.Contains("{{"))
|
|
|
|
{
|
|
|
|
parseSource(s2, "[[", "]]", ref sourceList);
|
|
|
|
parseSource(s2, "{{", "}}", ref sourceList);
|
|
|
|
}
|
|
|
|
else if (s2.Contains("="))
|
|
|
|
{
|
|
|
|
sourceList.Add(propertyValue);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sourceList.Add(s2);
|
|
|
|
}
|
|
|
|
sourceList.Sort();
|
|
|
|
|
|
|
|
propertyValue = sourceList.ToArray();
|
|
|
|
}
|
|
|
|
else if (propertyName == "profession")
|
|
|
|
{
|
|
|
|
// this is probably a comma-separated list
|
|
|
|
// it's used by the infobox to calculate prices
|
|
|
|
string[] list = (propertyValue as string).Split(new string[] { "," }, StringSplitOptions.None);
|
|
|
|
propertyValue = list;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
propertyValue = parseString(propertyValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
// fix price key name
|
|
|
|
propertyName = propertyName == "price" ? "sellprice" : propertyName;
|
|
|
|
|
|
|
|
|
|
|
|
// strip double apostrophes from strings
|
|
|
|
if (propertyValue is string && propertyValue.Contains("''"))
|
|
|
|
{
|
|
|
|
propertyValue = (propertyValue as string).Replace("''", "");
|
|
|
|
}
|
|
|
|
|
|
|
|
//ordProps.Add(propertyName, propertyValue);
|
|
|
|
ordProps[propertyName] = propertyValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// build description template
|
|
|
|
// {{Description|Prismatic%20Shard}}
|
|
|
|
string descTemplate = "{{Description|" + strItem + "}}";
|
|
|
|
|
|
|
|
// add a description
|
|
|
|
ordProps["description"] = Wikidata(descTemplate, "wikitext"); ;
|
|
|
|
|
|
|
|
// this is where we can alphabetize the properties if we really want to
|
|
|
|
//itm.ItemProps = new SortedDictionary<string,object>(dynProps);
|
|
|
|
itm = ordProps;
|
|
|
|
|
|
|
|
// add profession property where necessary
|
|
|
|
if (itm["name"].ToString() == "Dinosaur Egg")
|
|
|
|
{
|
|
|
|
itm["profession"] = new string[] { "Rancher", "Artisan" };
|
|
|
|
}
|
|
|
|
|
|
|
|
// if it's a fish, add the right professions
|
|
|
|
if (Category == "Fish")
|
|
|
|
{
|
|
|
|
itm["profession"] = new string[] { "Fisher", "Angler" };
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate quality prices
|
|
|
|
if ((string)itm["quality"] == "true" || (string)itm["iridium"] == "true")
|
|
|
|
{
|
|
|
|
GetPrices(ref itm);
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we can do shit with the price based on profession
|
|
|
|
if (itm["profession"] != null && itm["sellprice"] != null)
|
|
|
|
{
|
|
|
|
// calculate price based on profession
|
|
|
|
string[] professions = (string[])itm["profession"];
|
|
|
|
|
|
|
|
foreach (string profession in professions)
|
|
|
|
{
|
|
|
|
GetPrices(ref itm, profession.ToLower());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add a type of vegetable-p to hops, wheat, and tea leaves0
|
|
|
|
if (new[] { "Hops", "Wheat", "Tea Leaves" }.Contains(itm["name"].ToString()))
|
|
|
|
{
|
|
|
|
itm["type"] = "vegetable-p";
|
|
|
|
}
|
|
|
|
|
|
|
|
// add a type of vegetable to corn
|
|
|
|
if (itm["name"].ToString() == "Corn")
|
|
|
|
{
|
|
|
|
itm["type"] = "vegetable";
|
|
|
|
}
|
|
|
|
|
|
|
|
// add a type of flower to members of Flowers
|
|
|
|
if (Globals.Flowers.Contains(itm["name"].ToString()))
|
|
|
|
{
|
|
|
|
itm["type"] = "flower";
|
|
|
|
}
|
|
|
|
|
|
|
|
string artPrice(object p)
|
|
|
|
{
|
|
|
|
string pInt = Math.Truncate(Convert.ToInt32(p) * 1.4).ToString();
|
|
|
|
return pInt;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (itm["type"] != null)
|
|
|
|
{
|
|
|
|
// get the base sell price
|
|
|
|
int sprice = Convert.ToInt32(itm["sellprice"]);
|
|
|
|
|
|
|
|
// if it's a fruit or vegetable, calculate price of artisan good
|
|
|
|
// fruit
|
|
|
|
if (itm["type"].ToString() == "fruit")
|
|
|
|
{
|
|
|
|
// this can be made into jelly and wine
|
|
|
|
// wine is available in all qualities
|
|
|
|
// jelly = (1 * 2) + 50
|
|
|
|
// wine = 1 * 3
|
|
|
|
|
|
|
|
// jelly
|
|
|
|
itm["price_jelly"] = ((sprice * 2) + 50).ToString();
|
|
|
|
itm["price_jelly_artisan"] = artPrice(itm["price_jelly"]);
|
|
|
|
|
|
|
|
// wine
|
|
|
|
GetPrices(ref itm, "wine_");
|
|
|
|
}
|
|
|
|
// vegetable, vegetable-p
|
|
|
|
else if (itm["type"].ToString().StartsWith("vegetable"))
|
|
|
|
{
|
|
|
|
// this can be made into pickles and juice
|
|
|
|
// pickles = (1 * 2) + 50
|
|
|
|
// juice = 1 * 2.25
|
|
|
|
|
|
|
|
// pickles
|
|
|
|
itm["price_pickles"] = ((sprice * 2) + 50).ToString();
|
|
|
|
itm["price_pickles_artisan"] = artPrice(itm["price_pickles"]);
|
|
|
|
|
|
|
|
if (itm["type"].ToString() == "vegetable")
|
|
|
|
{
|
|
|
|
itm["price_juice"] = ((sprice * 2) + 50).ToString();
|
|
|
|
itm["price_juice_artisan"] = artPrice(itm["price_juice"]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// flower
|
|
|
|
else if (itm["type"].ToString() == "flower")
|
|
|
|
{
|
|
|
|
// 100g + (base x 2)
|
|
|
|
itm["price_honey"] = (100 + (sprice * 2)).ToString();
|
|
|
|
itm["price_honey_artisan"] = artPrice(itm["price_honey"]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there's at least one artisan good associated with this
|
|
|
|
// get that good's data and append it to the item
|
|
|
|
if (Globals.ArtisanGoods.Contains(itm["name"]))
|
|
|
|
{
|
|
|
|
// get the name of the artisan good from the dictionary
|
|
|
|
string ArtGoodName = Globals.ArtisanGoods[itm["name"].ToString()].ToString();
|
|
|
|
|
|
|
|
// add a property to the item for the name of the artisan good
|
|
|
|
itm["artisan_good"] = ArtGoodName;
|
|
|
|
|
|
|
|
// generate the infobox data for the artisan good
|
|
|
|
// we'll take the prices from this
|
|
|
|
OrderedDictionary ArtGood = ItemBox(ArtGoodName, Category);
|
|
|
|
|
|
|
|
// we can't easily filter an OrderedDictionary's keys
|
|
|
|
// convert list of keys to string array
|
|
|
|
string[] ArtGoodKeys = new string[ArtGood.Count];
|
|
|
|
// copy all the keys to the array
|
|
|
|
ArtGood.Keys.CopyTo(ArtGoodKeys, 0);
|
|
|
|
|
|
|
|
// filter for entities named "price_..."
|
|
|
|
IEnumerable<string> AGKeys = ArtGoodKeys.Where(agk => agk.Contains("price_"));
|
|
|
|
|
|
|
|
// for each key in the filtered list, retrieve the key-value pair from the infobox
|
|
|
|
foreach (string k in AGKeys)
|
|
|
|
{
|
|
|
|
int theValue = Convert.ToInt32(ArtGood[k]);
|
|
|
|
// this use the product name, we probably don't want his
|
|
|
|
string artName = (ArtGoodName.ToLower()).Replace(" ", "_") + "_";
|
|
|
|
// set this part of the key to "product" instead
|
|
|
|
artName = "product_";
|
|
|
|
string theKey = k.Replace("price_", "price_" + artName);
|
|
|
|
|
|
|
|
itm[theKey] = theValue.ToString(); ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//itm.Dump();
|
|
|
|
|
|
|
|
// add to object array
|
|
|
|
// AllItems.Add(itm);
|
|
|
|
// JsonConvert.SerializeObject(ordProps, Newtonsoft.Json.Formatting.Indented).Dump();
|
|
|
|
return ordProps;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Appends quality prices to item OrderedDictionary: GetPrices(ref item, string profession) // set profession to "wine_" for wine pricing
|
|
|
|
/// </summary>
|
|
|
|
static void GetPrices(ref OrderedDictionary itm, string pro = null)
|
|
|
|
{
|
|
|
|
// get the base price from the item
|
|
|
|
int price = Convert.ToInt32(itm["sellprice"]);
|
|
|
|
|
|
|
|
// set the base multiplier
|
|
|
|
decimal multi = 1;
|
|
|
|
|
|
|
|
// set the string prefix for key names
|
|
|
|
string pre = "price_";
|
|
|
|
|
|
|
|
// if a profession is defined, use its multiplier and add a prefix
|
|
|
|
if (pro != null)
|
|
|
|
{
|
|
|
|
if (pro == "wine_")
|
|
|
|
{
|
|
|
|
multi *= 3;
|
|
|
|
pre += pro;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
multi *= Convert.ToDecimal(Globals.Professions[pro]);
|
|
|
|
pre += pro.ToLower() + "_";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate prices and add key+value pairs to item
|
|
|
|
int i = 0;
|
|
|
|
foreach (DictionaryEntry qual in Globals.Quality)
|
|
|
|
{
|
|
|
|
string key = pre + i + "_" + qual.Key;
|
|
|
|
decimal newprice = price * Convert.ToDecimal(qual.Value) * multi;
|
|
|
|
itm[key] = Math.Truncate(newprice).ToString();
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate artisan prices for wine
|
|
|
|
if (pro == "wine_")
|
|
|
|
{
|
|
|
|
i = 0;
|
|
|
|
foreach (DictionaryEntry qual in Globals.Quality)
|
|
|
|
{
|
|
|
|
string key = pre + "artisan_" + i + "_" + qual.Key;
|
|
|
|
decimal bprice = Convert.ToDecimal(itm["price_wine_" + i + "_" + qual.Key]);
|
|
|
|
decimal newprice = bprice * Convert.ToDecimal(1.4);
|
|
|
|
itm[key] = Math.Truncate(newprice).ToString();
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((string)itm["iridium"] != "true")
|
|
|
|
{
|
|
|
|
itm.Remove(pre + "3_iridium");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Appends health and energy buffs to item OrderedDictionary: GetBuffs(ref item)
|
|
|
|
/// <summary>
|
|
|
|
static void GetBuffs(ref OrderedDictionary itm)
|
|
|
|
{
|
|
|
|
// get base edibility score
|
|
|
|
int basic = Convert.ToInt32(itm["edibility"]);
|
|
|
|
|
|
|
|
// if <= -300, shit ain't edible
|
|
|
|
if (basic <= -300)
|
|
|
|
{
|
|
|
|
itm["edible"] = "false";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
itm["edible"] = "true";
|
|
|
|
|
|
|
|
// if > -300 AND if < 0
|
|
|
|
if (basic > -300 && basic < 0)
|
|
|
|
{
|
|
|
|
foreach (DictionaryEntry m in Globals.BuffQuality)
|
|
|
|
{
|
|
|
|
string key = m.Key.ToString();
|
|
|
|
int quality = Convert.ToInt32(m.Value);
|
|
|
|
|
|
|
|
//now we do math for energy
|
|
|
|
int value = Convert.ToInt16(Math.Ceiling(basic * 2.5) + basic * quality);
|
|
|
|
|
|
|
|
itm["energy_" + m.Value + "_" + key] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (basic == 0)
|
|
|
|
{
|
|
|
|
// this shit's worthless, yo
|
|
|
|
itm["energy_base"] = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (basic > 0)
|
|
|
|
{
|
|
|
|
foreach (DictionaryEntry m in Globals.BuffQuality)
|
|
|
|
{
|
|
|
|
string key = m.Key.ToString();
|
|
|
|
int quality = Convert.ToInt32(m.Value);
|
|
|
|
|
|
|
|
// now we do math for energy
|
|
|
|
int evalue = Convert.ToInt16(Math.Truncate(Math.Ceiling(basic * 2.5) + basic * quality));
|
|
|
|
int hvalue = Convert.ToInt16(Math.Truncate(evalue * 0.45));
|
|
|
|
|
|
|
|
itm["energy_" + m.Value + "_" + key] = evalue;
|
|
|
|
itm["health_" + m.Value + "_" + key] = hvalue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// this is so wrong
|
|
|
|
// how the fuck did you even get here?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// function for returning wikitext from an API
|
|
|
|
static string Wikidata(string strInput, string strType)
|
|
|
|
{
|
|
|
|
string strURL = "https://stardewvalleywiki.com/mediawiki/api.php?action=query&prop=revisions&rvprop=content&format=json&rvsection=1&titles=";
|
|
|
|
|
|
|
|
// create the URL based on the type
|
|
|
|
// set the XML node path
|
|
|
|
switch (strType)
|
|
|
|
{
|
|
|
|
case "item":
|
|
|
|
strURL = "http://stardewvalleywiki.com/mediawiki/api.php?action=query&prop=revisions&rvprop=content&format=json&rvsection=0&titles=" + strInput;
|
|
|
|
// "[query][pages][$pageid$][revisions][0][*]";
|
|
|
|
break;
|
|
|
|
case "image":
|
|
|
|
strURL = "http://stardewvalleywiki.com/mediawiki/api.php?action=query&prop=imageinfo&iiprop=url&continue=&format=json&titles=File:" + strInput;
|
|
|
|
// "[query][pages][$pageid$][imageinfo][0][url]"; // this is the actual image file from the wiki
|
|
|
|
break;
|
|
|
|
case "category":
|
|
|
|
strURL = "https://stardewvalleywiki.com/mediawiki/api.php?action=query&prop=revisions&rvprop=content&format=json&rvsection=0&titles=Template:Navbox" + strInput;
|
|
|
|
// "[query][pages][$pageid$][revisions][0][*]";
|
|
|
|
break;
|
|
|
|
case "wikitext":
|
|
|
|
strURL = "https://stardewvalleywiki.com/mediawiki/api.php?action=expandtemplates&prop=wikitext&format=json&text=" + strInput;
|
|
|
|
// "[expandtemplates][wikitext]";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
string raw = new WebClient().DownloadString(strURL);
|
|
|
|
|
|
|
|
JObject obj = JObject.Parse(raw.ToString());
|
|
|
|
|
|
|
|
JObject objPage = obj;
|
|
|
|
string pageid = "";
|
|
|
|
|
|
|
|
if (strType != "wikitext")
|
|
|
|
{
|
|
|
|
objPage = (JObject)obj["query"]["pages"];
|
|
|
|
|
|
|
|
pageid = "";
|
|
|
|
|
|
|
|
foreach (JProperty p in objPage.Properties())
|
|
|
|
{
|
|
|
|
if (pageid == "")
|
|
|
|
{
|
|
|
|
pageid = p.Name;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (strType)
|
|
|
|
{
|
|
|
|
case "item":
|
|
|
|
case "category":
|
|
|
|
return objPage[pageid]["revisions"][0]["*"].ToString();
|
|
|
|
case "image":
|
|
|
|
return objPage[pageid]["imageinfo"][0]["url"].ToString();
|
|
|
|
case "wikitext":
|
|
|
|
return objPage["expandtemplates"]["wikitext"].ToString();
|
|
|
|
default:
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// function for parsing strings
|
|
|
|
static string parseString(string s)
|
|
|
|
{
|
|
|
|
// the default output is the input string
|
|
|
|
string result = s;
|
|
|
|
|
|
|
|
// replace all HTML entities with characters
|
|
|
|
result = HttpUtility.HtmlDecode(s);
|
|
|
|
|
|
|
|
// if the string contains wikitext markup, parse it
|
|
|
|
// otherwise, return propertyValue
|
|
|
|
if (new string[] { "[[", "{{" }.Any(str => s.Contains(str)))
|
|
|
|
{
|
|
|
|
if (s.Contains("[["))
|
|
|
|
{
|
|
|
|
// this string has one or more [[wikilinks]]
|
|
|
|
// extract all [[wikilinks]]
|
|
|
|
Match m = Regex.Match(s, @"\[\[(.+?)\]\]"); // .+? means lazy (ungreedy) matching
|
|
|
|
|
|
|
|
while (m.Success)
|
|
|
|
{
|
|
|
|
// check for File: first
|
|
|
|
// then check for pipe without File:
|
|
|
|
string linktext = m.Value;
|
|
|
|
|
|
|
|
if (linktext.Contains("File"))
|
|
|
|
{
|
|
|
|
// this link contains a file. do things.
|
|
|
|
if (linktext.Contains("Level"))
|
|
|
|
{
|
|
|
|
// [[File:Fishing Skill Icon.png|24px|link=]] [[Fishing]] Level 2
|
|
|
|
// Fishing]] Level 2
|
|
|
|
// retrieve whole wikilink and concatenate second and third values
|
|
|
|
Match mx = Regex.Match(linktext, @"\[\[File.+?\]\] \[\[(.+)\]\]( Level [0-9]+)", RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
// for every match, perform string replacement
|
|
|
|
while (mx.Success)
|
|
|
|
{
|
|
|
|
// Fishing Level 2
|
|
|
|
string strOld = mx.Groups[1].Captures[0].Value;
|
|
|
|
string strNew = mx.Groups[2].Captures[0].Value;
|
|
|
|
|
|
|
|
result = result.Replace(strOld, strNew);
|
|
|
|
|
|
|
|
mx = mx.NextMatch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// [[File:Shirt001.png|center]]
|
|
|
|
// [[File:Axe.png]]
|
|
|
|
// retrieve whole wikilink, second capture group is the filename
|
|
|
|
Match mx = Regex.Match(linktext, @"\[\[File:([\w -_.]+).*?\]\]", RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
// for every match, perform string replacement
|
|
|
|
while (mx.Success)
|
|
|
|
{
|
|
|
|
// Shirt001.png
|
|
|
|
// Axe.png
|
|
|
|
string strOld = mx.Groups[0].Captures[0].Value;
|
|
|
|
string strNew = mx.Groups[1].Captures[0].Value;
|
|
|
|
|
|
|
|
result = result.Replace(strOld, strNew);
|
|
|
|
|
|
|
|
mx = mx.NextMatch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// [[The Mines]]
|
|
|
|
// [[Random Events#Meteorite|meteorite]]
|
|
|
|
// this is a regular ol' link
|
|
|
|
if (linktext.Contains("|"))
|
|
|
|
{
|
|
|
|
string[] linkProps = m.Value.Split(new string[] { "|" }, StringSplitOptions.None);
|
|
|
|
result = result.Replace(m.Value, linkProps[1]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result = result.Replace(m.Value, m.Value.Substring(2, m.Value.Length - 3));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m = m.NextMatch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s.Contains("{{"))
|
|
|
|
{
|
|
|
|
// this string has one or more {{wikitext templates}}
|
|
|
|
// extract all {{wikitext templates}}
|
|
|
|
|
|
|
|
Match m = Regex.Match(s, @"\{\{(.+?)\}\}"); // .+? means lazy (ungreedy) matching
|
|
|
|
|
|
|
|
while (m.Success)
|
|
|
|
{
|
|
|
|
// this is the raw wikitext of the match
|
|
|
|
string wikitext = m.Value;
|
|
|
|
|
|
|
|
// this is the string minus the start and end brackets
|
|
|
|
string input = wikitext.Substring(2, wikitext.Length - 4);
|
|
|
|
|
|
|
|
// return this if all else fails
|
|
|
|
result = input;
|
|
|
|
|
|
|
|
// we can just split by the pipe for this - all {{wikitext templates}} use pipe as the delimiter
|
|
|
|
string[] segments = input.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
|
|
|
// for case-insensitive string.Contains()
|
|
|
|
string templateName = segments[0].ToLower();
|
|
|
|
string littletext = wikitext.ToLower();
|
|
|
|
|
|
|
|
if (segments.Count() > 1)
|
|
|
|
{
|
|
|
|
if (templateName == "name")
|
|
|
|
{
|
|
|
|
|
|
|
|
if (segments.Count() == 2)
|
|
|
|
{
|
|
|
|
// return second segment as-is
|
|
|
|
result = segments[1];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// {{name|Mining|Level 9|class=inline}}
|
|
|
|
// everything past the third segment (e.g. 50) can be ignored
|
|
|
|
if (littletext.Contains("level"))
|
|
|
|
{
|
|
|
|
// {{name|Farming|Level 3|image=Farming Skill Icon.png}}
|
|
|
|
// Farming Level 3
|
|
|
|
// test with Equipment
|
|
|
|
result = segments[1] + " " + segments[2];
|
|
|
|
}
|
|
|
|
else if (littletext.Contains("+"))
|
|
|
|
{
|
|
|
|
// {{name|Defense|+3}
|
|
|
|
// +3 Defense
|
|
|
|
// test with Clothing and Weapons
|
|
|
|
result = segments[2] + " " + segments[1];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// {{name|Omni Geode|50|...}}
|
|
|
|
// Omni Geode {50)
|
|
|
|
result = segments[1] + " (" + segments[2] + ")";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (templateName == "npc")
|
|
|
|
{
|
|
|
|
// {{NPC|Jodi|Mother}}
|
|
|
|
// Jodi (Mother)
|
|
|
|
if (segments.Count() > 2)
|
|
|
|
result = segments[1] + " (" + segments[2] + ")";
|
|
|
|
else
|
|
|
|
result = segments[1];
|
|
|
|
}
|
|
|
|
else if (templateName == "price")
|
|
|
|
{
|
|
|
|
// {{Price|30|Currency}}
|
|
|
|
// JOPK, Qi, Star Token (if third parameter is empty, this is regular gold)
|
|
|
|
result = segments[1];
|
|
|
|
|
|
|
|
if (segments.Count() > 2)
|
|
|
|
{
|
|
|
|
switch (segments[2])
|
|
|
|
{
|
|
|
|
case "JOPK":
|
|
|
|
result += " tokens";
|
|
|
|
break;
|
|
|
|
case "Qi":
|
|
|
|
result += " Qui coins";
|
|
|
|
break;
|
|
|
|
case "Token":
|
|
|
|
result += " star tokens";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result += "g";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (templateName == "description")
|
|
|
|
{
|
|
|
|
// {{Description|Wild Horseradish}}
|
|
|
|
// {{Description|Recipe|Lucky Lunch}}
|
|
|
|
|
|
|
|
// this returns a description string
|
|
|
|
// use this API call:
|
|
|
|
// https://stardewvalleywiki.com/mediawiki/api.php?action=expandtemplates&prop=wikitext&format=json&text=...
|
|
|
|
// result["expandtemplates"]["wikitext"]
|
|
|
|
|
|
|
|
result = Wikidata(wikitext, "wikitext");
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// this is wrong
|
|
|
|
result += " ***BAD INPUT***";
|
|
|
|
}
|
|
|
|
|
|
|
|
m = m.NextMatch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clean <p> tags from result
|
|
|
|
result = result.Replace("<p>", @"\n");
|
|
|
|
result = result.Replace("</p>", "");
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// function for parsing single line values
|
|
|
|
static string parseStringOld(string propertyValue)
|
|
|
|
{
|
|
|
|
// {{name=Skill|Level #|imge=...}}
|
|
|
|
string regex_new = @"(?<={{name\|)\w+\|Level [0-9]+(?=\|.+)";
|
|
|
|
Regex rgx_new = new Regex(regex_new, RegexOptions.IgnoreCase);
|
|
|
|
Match m_new = rgx_new.Match(propertyValue);
|
|
|
|
|
|
|
|
// [[File:...]] [[Skill]] Level #
|
|
|
|
string regex_old = @"(?!\[\[File.+\]\] \[\[)\w+\]\] Level [0-9]+";
|
|
|
|
Regex rgx_old = new Regex(regex_old, RegexOptions.IgnoreCase);
|
|
|
|
Match m_old = rgx_old.Match(propertyValue);
|
|
|
|
|
|
|
|
// {{NPC|...|...[[..]]}}
|
|
|
|
// {{NPC|...|...}}
|
|
|
|
string regex_npc = @"(?<=\{\{NPC\|)[A-Za-z0-9 \-\+\|]+(?=\[\[|\}\})";
|
|
|
|
Regex rgx_npc = new Regex(regex_npc, RegexOptions.IgnoreCase);
|
|
|
|
Match m_npc = rgx_npc.Match(propertyValue);
|
|
|
|
|
|
|
|
// {{description|...}}
|
|
|
|
string regex_desc = @"(?<=\{\{description\|)[A-Za-z0-9 \-\+\|]+(?=|\}\})";
|
|
|
|
Regex rgx_desc = new Regex(regex_desc, RegexOptions.IgnoreCase);
|
|
|
|
Match m_desc = rgx_desc.Match(propertyValue);
|
|
|
|
|
|
|
|
// clean up wikilink syntax
|
|
|
|
string regex_link = @"\[\[.+\|(\w+)\]\]";
|
|
|
|
Regex rgx_link = new Regex(regex_link, RegexOptions.IgnoreCase);
|
|
|
|
Match m_link = rgx_link.Match(propertyValue);
|
|
|
|
|
|
|
|
// remove wikilink brackets
|
|
|
|
string regex_bckt = @"(?<=\[\[)[\w\s']+(?=\]\])";
|
|
|
|
Regex rgx_bckt = new Regex(regex_bckt, RegexOptions.IgnoreCase);
|
|
|
|
Match m_bckt = rgx_bckt.Match(propertyValue);
|
|
|
|
|
|
|
|
string result = propertyValue;
|
|
|
|
|
|
|
|
if (m_new.Success)
|
|
|
|
{
|
|
|
|
result = m_new.Value.Replace("|", " ");
|
|
|
|
}
|
|
|
|
else if (m_old.Success)
|
|
|
|
{
|
|
|
|
result = m_old.Value.Replace("]]", "");
|
|
|
|
}
|
|
|
|
else if (m_npc.Success)
|
|
|
|
{
|
|
|
|
result = m_npc.Value.Replace("|", " - ");
|
|
|
|
if (result.Contains("+"))
|
|
|
|
{
|
|
|
|
result += "hearts";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (m_desc.Success)
|
|
|
|
{
|
|
|
|
result = m_desc.Value;
|
|
|
|
}
|
|
|
|
else if (m_link.Success)
|
|
|
|
{
|
|
|
|
result = propertyValue.Replace(m_link.Groups[0].Value, m_link.Groups[1].Value);
|
|
|
|
}
|
|
|
|
else if (m_bckt.Success)
|
|
|
|
{
|
|
|
|
result = (propertyValue.Replace("[", "")).Replace("]", "");
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// function for parsing lists into arrays
|
|
|
|
static void parseSource(string strInput, string strStart, string strEnd, ref List<string> lstSauce)
|
|
|
|
{
|
|
|
|
// add blacklisted entries here
|
|
|
|
List<String> avoid = new List<String>() { };
|
|
|
|
|
|
|
|
// clean string of extraneous characters that break shit
|
|
|
|
strInput = strInput.Replace("{{!}}", "");
|
|
|
|
|
|
|
|
while (strInput.IndexOf(strStart) > -1)
|
|
|
|
{
|
|
|
|
// trim any whitespace
|
|
|
|
strInput = strInput.Trim();
|
|
|
|
|
|
|
|
// find the first instance of the start characters
|
|
|
|
int start = strInput.IndexOf(strStart);
|
|
|
|
|
|
|
|
// find the first instance of the end characters
|
|
|
|
int end = strInput.IndexOf(strEnd) + 2;
|
|
|
|
|
|
|
|
// get the length of the string to be extracted
|
|
|
|
int length = end - start;
|
|
|
|
|
|
|
|
// extract the string
|
|
|
|
string sub = strInput.Substring(start, length);
|
|
|
|
|
|
|
|
// remove the start and end characters
|
|
|
|
sub = sub.Substring(2, (sub.Length - 4));
|
|
|
|
|
|
|
|
// remove the extracted string
|
|
|
|
strInput = strInput.Remove(start, length);
|
|
|
|
|
|
|
|
// clean up the substring
|
|
|
|
// don't include items that match the blacklist
|
|
|
|
|
|
|
|
//"name|Fishing|Level 2|image=Fishing Skill Icon.png"
|
|
|
|
//"name|Bug Meat|1"
|
|
|
|
|
|
|
|
if (!avoid.Any(sub.Contains))
|
|
|
|
{
|
|
|
|
if (sub.Contains("|"))
|
|
|
|
{
|
|
|
|
string[] strSep = { "|" };
|
|
|
|
string[] subSplit = sub.Split(strSep, System.StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
if (sub.Contains("Bundle"))
|
|
|
|
{
|
|
|
|
sub = subSplit[1] + " Bundle";
|
|
|
|
}
|
|
|
|
else if (strInput.Contains("ingredients"))
|
|
|
|
{
|
|
|
|
sub = subSplit[1] + " (" + subSplit[2] + ")";
|
|
|
|
}
|
|
|
|
else if (strInput.Contains("recipe"))
|
|
|
|
{
|
|
|
|
Console.Write(sub, " | ");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sub = subSplit[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lstSauce.Add(sub);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// global constants for different functions
|
|
|
|
/// </summary>
|
|
|
|
public class Globals
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// list of blacklisted items by name - these will be skipped
|
|
|
|
/// </summary>
|
|
|
|
public static List<String> avoid = new List<String>
|
|
|
|
{
|
|
|
|
"Axes",
|
|
|
|
"Hoes",
|
|
|
|
"Pickaxes",
|
|
|
|
"Trash Cans",
|
|
|
|
"Watering Cans"
|
|
|
|
};
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// list of categories for navbox wikitext parsing
|
|
|
|
/// </summary>
|
|
|
|
public static string[] Categories =
|
|
|
|
{
|
|
|
|
"Animals", // 0
|
|
|
|
"Artifacts", // 1
|
|
|
|
"Artisan Goods", // 2
|
|
|
|
"Buildings", // 3
|
|
|
|
"Clothing", // 4
|
|
|
|
"Crop", // 5
|
|
|
|
"Decor", // 6
|
|
|
|
"Equipment", // 7
|
|
|
|
"Fish", // 8
|
|
|
|
"Foraging", // 9
|
|
|
|
"Furniture", // 10
|
|
|
|
"Ingredients", // 11
|
|
|
|
"Lighting", // 12
|
|
|
|
"Minerals", // 13
|
|
|
|
"Monsters", // 14
|
|
|
|
"Recipes", // 15
|
|
|
|
"Resources", // 16
|
|
|
|
"Seeds", // 17
|
|
|
|
"Tree", // 18
|
|
|
|
"Tools", // 19
|
|
|
|
"Villagers", // 20
|
|
|
|
"Warp Totems", // 21
|
|
|
|
"Weapons" // 22
|
|
|
|
};
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Price multiplier for each Quality rating
|
|
|
|
/// </summary>
|
|
|
|
public static OrderedDictionary Quality = new OrderedDictionary
|
|
|
|
{
|
|
|
|
{ "base", 1 },
|
|
|
|
{ "silver", 1.25 },
|
|
|
|
{ "gold", 1.5 },
|
|
|
|
{ "iridium", 2 }
|
|
|
|
};
|
|
|
|
|
|
|
|
public static OrderedDictionary BuffQuality = new OrderedDictionary
|
|
|
|
{
|
|
|
|
{ "base", 0 },
|
|
|
|
{ "silver", 1 },
|
|
|
|
{ "gold", 2 },
|
|
|
|
{ "iridium", 4 }
|
|
|
|
};
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Price multiplier for each Profession
|
|
|
|
/// </summary>
|
|
|
|
public static OrderedDictionary Professions = new OrderedDictionary
|
|
|
|
{
|
|
|
|
{ "artisan", 1.4 },
|
|
|
|
{ "rancher", 1.2 },
|
|
|
|
{ "gemologist", 1.3 },
|
|
|
|
{ "tiller", 1.1 },
|
|
|
|
{ "blacksmith", 1.5 },
|
|
|
|
{ "forester", 1.25 },
|
|
|
|
{ "fisher", 1.25 },
|
|
|
|
{ "angler", 1.5 }
|
|
|
|
};
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Produce-to-ArtisanGood relationship
|
|
|
|
/// </summary>
|
|
|
|
public static OrderedDictionary ArtisanGoods = new OrderedDictionary
|
|
|
|
{
|
|
|
|
{ "Hops", "Pale Ale" },
|
|
|
|
{ "Wheat", "Beer" },
|
|
|
|
{ "Honey", "Mead" },
|
|
|
|
{ "Milk", "Cheese" },
|
|
|
|
{ "Large Milk", "Cheese" },
|
|
|
|
{ "Goat Milk", "Goat Cheese" },
|
|
|
|
{ "Large Goat Milk", "Goat Cheese" },
|
|
|
|
{ "Coffee Bean", "Coffee" },
|
|
|
|
{ "Tea Leaves", "Green Tea" },
|
|
|
|
{ "Wool", "Cloth" },
|
|
|
|
{ "Egg", "Mayonnaise" },
|
|
|
|
{ "Large Egg", "Mayonnaise" },
|
|
|
|
{ "Void Egg", "Void Mayonnaise" },
|
|
|
|
{ "Dinosaur Egg", "Dinosaur Mayonnaise" },
|
|
|
|
{ "Truffle", "Truffle Oil" },
|
|
|
|
{ "Corn", "Oil" },
|
|
|
|
{ "Sunflower", "Oil" },
|
|
|
|
{ "Sunflower Seeds", "Oil" },
|
|
|
|
{ "Sturgeon Roe", "Caviar" },
|
|
|
|
{ "Roe", "Aged Roe" },
|
|
|
|
};
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// List of flowers for honey
|
|
|
|
/// </summary>
|
|
|
|
public static List<string> Flowers = new List<string>
|
|
|
|
{
|
|
|
|
{ "Blue Jazz" },
|
|
|
|
{ "Fairy Rose" },
|
|
|
|
{ "Poppy" },
|
|
|
|
{ "Summer Spangle" },
|
|
|
|
{ "Sunflower" },
|
|
|
|
{ "Tulip" }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|