tibiawikisql 6.1.0__tar.gz → 6.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/CHANGELOG.rst +16 -0
  2. {tibiawikisql-6.1.0/tibiawikisql.egg-info → tibiawikisql-6.2.1}/PKG-INFO +1 -1
  3. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/requirements.txt +2 -1
  4. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/__init__.py +2 -2
  5. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/__main__.py +159 -27
  6. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/creature.py +4 -0
  7. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/imbuement.py +4 -0
  8. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/item.py +13 -10
  9. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/npc.py +8 -4
  10. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/schema.py +3 -0
  11. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/utils.py +21 -6
  12. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1/tibiawikisql.egg-info}/PKG-INFO +1 -1
  13. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/requires.txt +1 -0
  14. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/LICENSE +0 -0
  15. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/MANIFEST.in +0 -0
  16. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/README.md +0 -0
  17. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/setup.cfg +0 -0
  18. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/setup.py +0 -0
  19. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/__init__.py +0 -0
  20. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/test_models_utils.py +0 -0
  21. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/tests_models.py +0 -0
  22. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/tests_utils.py +0 -0
  23. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tests/tests_wikiapi.py +0 -0
  24. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/api.py +0 -0
  25. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/database.py +0 -0
  26. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/__init__.py +0 -0
  27. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/abc.py +0 -0
  28. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/achievement.py +0 -0
  29. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/charm.py +0 -0
  30. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/house.py +0 -0
  31. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/mount.py +0 -0
  32. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/outfit.py +0 -0
  33. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/quest.py +0 -0
  34. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/spell.py +0 -0
  35. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/update.py +0 -0
  36. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql/models/world.py +0 -0
  37. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/SOURCES.txt +0 -0
  38. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/dependency_links.txt +0 -0
  39. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/entry_points.txt +0 -0
  40. {tibiawikisql-6.1.0 → tibiawikisql-6.2.1}/tibiawikisql.egg-info/top_level.txt +0 -0
@@ -2,6 +2,22 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ .. 6.2.1
6
+
7
+ 6.2.1 (2024-06-27)
8
+ ==================
9
+ - Fix database generation failing due to invalid date format.
10
+
11
+
12
+ .. 6.2.0
13
+
14
+ 6.2.0 (2022-07-23)
15
+ ==================
16
+ - Added `bosstiary_class` to creatures.
17
+ - NPC Offers are read from the ItemPrices data list.
18
+ - NPC Spell offers are read from the ItemPrices spell list.
19
+ - Performance improvements.
20
+
5
21
  .. 6.1.0
6
22
 
7
23
  6.1.0 (2022-01-04)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tibiawikisql
3
- Version: 6.1.0
3
+ Version: 6.2.1
4
4
  Summary: Python script that generates a SQLite database from TibiaWiki articles
5
5
  Home-page: https://github.com/Galarzaa90/tibiawiki-sql
6
6
  Author: Galarzaa90
@@ -1,4 +1,5 @@
1
1
  requests
2
2
  click
3
3
  colorama
4
- mwparserfromhell
4
+ mwparserfromhell
5
+ lupa
@@ -15,10 +15,10 @@
15
15
  """API that reads and parses information from `TibiaWiki <https://tibiawiki.fandom.com>`_."""
16
16
 
17
17
  __author__ = "Allan Galarza"
18
- __copyright__ = "Copyright 2021 Allan Galarza"
18
+ __copyright__ = "Copyright 2024 Allan Galarza"
19
19
 
20
20
  __license__ = "Apache 2.0"
21
- __version__ = "6.1.0"
21
+ __version__ = "6.2.1"
22
22
 
23
23
  from tibiawikisql import models
24
24
  from tibiawikisql.api import Article, Image, WikiClient, WikiEntry
@@ -11,11 +11,12 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
-
14
+ import collections
15
15
  import datetime
16
16
  import json
17
17
  import os
18
18
  import platform
19
+ import re
19
20
  import sqlite3
20
21
  import time
21
22
  from typing import Optional, Type
@@ -24,6 +25,7 @@ import click
24
25
  import colorama
25
26
  import requests
26
27
  from colorama import Fore, Style
28
+ from lupa import LuaRuntime
27
29
 
28
30
  from tibiawikisql import Image, WikiClient, __version__, models, schema
29
31
  from tibiawikisql.models import abc
@@ -34,6 +36,8 @@ DATABASE_FILE = "tibiawiki.db"
34
36
 
35
37
  colorama.init()
36
38
 
39
+ lua = LuaRuntime()
40
+
37
41
 
38
42
  def progress_bar(iterable, label, length, **kwargs):
39
43
  return click.progressbar(iterable=iterable, length=length, label=label, fill_char="█", empty_char="░", width=10,
@@ -53,23 +57,24 @@ class Category:
53
57
 
54
58
  Class for internal use only, for easier autocompletion and maintenance."""
55
59
  def __init__(self, name: Optional[str], model: Type[abc.Row] = None, *, no_images=False, extension=".gif",
56
- include_deprecated=False, no_title=False):
60
+ include_deprecated=False, no_title=False, generate_map=False):
57
61
  self.name = name
58
62
  self.model = model
59
63
  self.no_images = no_images
60
64
  self.extension = extension
61
65
  self.include_deprecated = include_deprecated
62
66
  self.no_title = no_title
67
+ self.generate_map = generate_map
63
68
 
64
69
 
65
70
  categories = {
66
71
  "achievements": Category("Achievements", models.Achievement, no_images=True),
67
- "spells": Category("Spells", models.Spell),
68
- "items": Category("Objects", models.Item),
69
- "creatures": Category("Creatures", models.Creature),
72
+ "spells": Category("Spells", models.Spell, generate_map=True),
73
+ "items": Category("Objects", models.Item, generate_map=True),
74
+ "creatures": Category("Creatures", models.Creature, generate_map=True),
70
75
  "books": Category("Book Texts", models.Book, no_images=True),
71
76
  "keys": Category("Keys", models.Key, no_images=True),
72
- "npcs": Category("NPCs", models.Npc),
77
+ "npcs": Category("NPCs", models.Npc, generate_map=True),
73
78
  "imbuements": Category("Imbuements", models.Imbuement, extension=".png"),
74
79
  "quests": Category("Quest Overview Pages", models.Quest, no_images=True),
75
80
  "house": Category("Player-Ownable Buildings", models.House, no_images=True),
@@ -111,6 +116,9 @@ def generate(skip_images, db_name, skip_deprecated):
111
116
  if not issubclass(model, abc.Parseable):
112
117
  continue
113
118
  titles = [a.title for a in data_store[key]]
119
+
120
+ if value.generate_map:
121
+ data_store[f"{key}_map"] = {}
114
122
  unparsed = []
115
123
  exec_time = time.perf_counter()
116
124
  generator = WikiClient.get_articles(titles)
@@ -120,6 +128,8 @@ def generate(skip_images, db_name, skip_deprecated):
120
128
  entry = model.from_article(article)
121
129
  if entry is not None:
122
130
  entry.insert(conn)
131
+ if value.generate_map:
132
+ data_store[f"{key}_map"][entry.title.lower()] = entry.article_id
123
133
  else:
124
134
  unparsed.append(article.title)
125
135
  if unparsed:
@@ -131,7 +141,9 @@ def generate(skip_images, db_name, skip_deprecated):
131
141
  for position in rashid_positions:
132
142
  position.insert(conn)
133
143
 
134
- generate_loot_statistics(conn)
144
+ generate_item_offers(conn, data_store)
145
+ generate_spell_offers(conn, data_store)
146
+ generate_loot_statistics(conn, data_store)
135
147
 
136
148
  if not skip_images:
137
149
  with conn:
@@ -154,45 +166,165 @@ def generate(skip_images, db_name, skip_deprecated):
154
166
  click.echo(f"Command finished in {dt:.2f} seconds.")
155
167
 
156
168
 
157
- def generate_loot_statistics(conn: sqlite3.Connection):
169
+ link_pattern = re.compile(r"(?:(?P<price>\d+))?\s?\[\[([^\]|]+)")
170
+
171
+
172
+ def generate_spell_offers(conn: sqlite3.Connection, data_store):
173
+ if "npcs_map" not in data_store or "spells_map" not in data_store:
174
+ return
175
+ start_time = time.perf_counter()
176
+ article = WikiClient.get_article("Module:ItemPrices/spelldata")
177
+ data = lua.execute(article.content)
178
+
179
+ spell_offers = []
180
+ not_found_store = collections.defaultdict(set)
181
+ spells = list(data.items())
182
+ with progress_bar(spells, "Fetching spell offers", len(spells)) as bar:
183
+ for name, table in bar:
184
+ spell_id = data_store["spells_map"].get(name.lower())
185
+ if spell_id is None:
186
+ not_found_store["spell"].add(name)
187
+ continue
188
+ spell_vocations = list((table["vocation"].values()))
189
+ for npc, vocation in table["sellers"].items():
190
+ npc_id = data_store["npcs_map"].get(npc.lower())
191
+ if npc_id is None:
192
+ not_found_store["item"].add(npc)
193
+ continue
194
+ if isinstance(vocation, bool):
195
+ npc_vocations = spell_vocations
196
+ elif isinstance(vocation, str):
197
+ npc_vocations = [vocation]
198
+ else:
199
+ npc_vocations = list(vocation.values())
200
+ spell_offers.append((
201
+ npc_id,
202
+ spell_id,
203
+ "Knight" in npc_vocations,
204
+ "Sorcerer" in npc_vocations,
205
+ "Druid" in npc_vocations,
206
+ "Paladin" in npc_vocations,
207
+ ))
208
+ with conn:
209
+ conn.execute("DELETE FROM npc_spell")
210
+ conn.executemany("INSERT INTO npc_spell(npc_id, spell_id, knight, sorcerer, paladin, druid) VALUES(?, ?, ?, ?, ?, ?)", spell_offers)
211
+ if not_found_store["spell"]:
212
+ unknonw_spells = not_found_store["spell"]
213
+ click.echo(f"{Fore.RED}Could not parse offers for {len(unknonw_spells):,} spell.{Style.RESET_ALL}")
214
+ click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknonw_spells)}{Style.RESET_ALL}")
215
+ if not_found_store["npc"]:
216
+ unknown_npcs = not_found_store["npc"]
217
+ click.echo(f"{Fore.RED}Could not parse offers of {len(unknown_npcs):,} npcs.{Style.RESET_ALL}")
218
+ click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknown_npcs)}{Style.RESET_ALL}")
219
+
220
+
221
+ def generate_item_offers(conn: sqlite3.Connection, data_store):
222
+ if "npcs_map" not in data_store or "items_map" not in data_store:
223
+ return
224
+ start_time = time.perf_counter()
225
+ article = WikiClient.get_article("Module:ItemPrices/data")
226
+ data = lua.execute(article.content)
227
+
228
+ sell_offers = []
229
+ buy_offers = []
230
+ not_found_store = collections.defaultdict(set)
231
+ npc_offers = list(data.items())
232
+ with progress_bar(npc_offers, "Fetching NPC offers", len(npc_offers)) as bar:
233
+ for name, table in bar:
234
+ npc_id = data_store["npcs_map"].get(name.lower())
235
+ if npc_id is None:
236
+ not_found_store["npc"].add(name)
237
+ continue
238
+ if "sells" in table:
239
+ process_offer_list(npc_id, table["sells"], sell_offers, data_store, not_found_store)
240
+ if "buys" in table:
241
+ process_offer_list(npc_id, table["buys"], buy_offers, data_store, not_found_store)
242
+ with conn:
243
+ conn.execute("DELETE FROM npc_offer_sell")
244
+ conn.execute("DELETE FROM npc_offer_buy")
245
+ conn.executemany("INSERT INTO npc_offer_sell(npc_id, value, item_id, currency_id) VALUES(?, ?, ?, ?)", sell_offers)
246
+ conn.executemany("INSERT INTO npc_offer_buy(npc_id, value, item_id, currency_id) VALUES(?, ?, ?, ?)", buy_offers)
247
+ dt = (time.perf_counter() - start_time)
248
+ total_offers = len(sell_offers) + len(buy_offers)
249
+ if not_found_store["npc"]:
250
+ unknonw_npcs = not_found_store["npc"]
251
+ click.echo(f"{Fore.RED}Could not parse offers for {len(unknonw_npcs):,} npcs.{Style.RESET_ALL}")
252
+ click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknonw_npcs)}{Style.RESET_ALL}")
253
+ if not_found_store["item"]:
254
+ unknonw_items = not_found_store["item"]
255
+ click.echo(f"{Fore.RED}Could not parse offers for {len(unknonw_items):,} items.{Style.RESET_ALL}")
256
+ click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknonw_items)}{Style.RESET_ALL}")
257
+ click.echo(f"{Fore.GREEN}\tSaved {total_offers:,} NPC offers in {dt:.2f} seconds.")
258
+
259
+
260
+ def process_offer_list(npc_id, array, list_store, data_store, not_found):
261
+ for lua_item in array.values():
262
+ data = dict(lua_item.items())
263
+ price = data["price"]
264
+ currency = data.get("currency", "gold coin")
265
+ if not isinstance(price, int):
266
+ m = link_pattern.search(price)
267
+ price = int(m.group("price") or 1)
268
+ currency = m.group(2)
269
+ item_name = data["item"]
270
+ currency_name = currency
271
+ item_id = data_store["items_map"].get(item_name.lower())
272
+ currency_id = data_store["items_map"].get(currency_name.lower())
273
+ if currency_id is None:
274
+ not_found["item"].add(currency_name)
275
+ continue
276
+ if item_id is None:
277
+ not_found["item"].add(item_name)
278
+ continue
279
+ list_store.append((
280
+ npc_id,
281
+ price,
282
+ item_id,
283
+ currency_id
284
+ ))
285
+
286
+
287
+ def generate_loot_statistics(conn: sqlite3.Connection, data_store):
158
288
  c = conn.cursor()
159
289
  try:
160
290
  results = conn.execute("SELECT title FROM creature")
161
291
  titles = [f"Loot Statistics:{t[0]}" for t in results]
162
292
  start_time = time.perf_counter()
293
+ unknown_items = set()
163
294
  with progress_bar(WikiClient.get_articles(titles), "Fetching loot statistics", len(titles),
164
295
  item_show_func=article_show) as bar:
165
296
  for article in bar:
166
297
  if article is None:
167
298
  continue
168
299
  creature_title = article.title.replace("Loot Statistics:", "")
169
- c.execute("SELECT article_id from creature WHERE title = ?", (creature_title,))
170
- result = c.fetchone()
171
- if result is None:
300
+ creature_id = data_store["creatures_map"].get(creature_title.lower())
301
+ if creature_id is None:
172
302
  # This could happen if a creature's article was deleted but its Loot Statistics weren't
173
303
  continue
174
- creature_id = result[0]
175
- # Most loot statistics contain stats for older versions too, we onl care about the latest version.
304
+ # Most loot statistics contain stats for older versions too, we only care about the latest version.
176
305
  kills, loot_stats = parse_loot_statistics(article.content)
177
306
  loot_items = []
178
307
  for entry in loot_stats:
179
- item = entry["item"]
180
- times = entry["times"]
181
- amount = entry.get("amount", 1)
182
- c.execute("SELECT article_id FROM item WHERE title LIKE ?", (item,))
183
- result = c.fetchone()
184
- if result is None:
185
- continue
186
- item_id = result[0]
187
- percentage = min(int(times) / kills * 100, 100)
188
- _min, _max = parse_min_max(amount)
189
- loot_items.append((creature_id, item_id, percentage, _min, _max))
190
- # We delete any duplicate record that was added from the creature's article's loot if it exists
191
- c.execute("DELETE FROM creature_drop WHERE creature_id = ? AND item_id = ?",
192
- (creature_id, item_id))
308
+ if entry:
309
+ item = entry["item"]
310
+ times = entry["times"]
311
+ amount = entry.get("amount", 1)
312
+ item_id = data_store["items_map"].get(item.lower())
313
+ if item_id is None:
314
+ unknown_items.add(item)
315
+ continue
316
+ percentage = min(int(times) / kills * 100, 100)
317
+ _min, _max = parse_min_max(amount)
318
+ loot_items.append((creature_id, item_id, percentage, _min, _max))
319
+ # We delete any duplicate record that was added from the creature's article's loot if it exists
320
+ c.execute("DELETE FROM creature_drop WHERE creature_id = ? AND item_id = ?",
321
+ (creature_id, item_id))
193
322
  c.executemany("INSERT INTO creature_drop(creature_id, item_id, chance, min, max) VALUES(?,?,?,?,?)",
194
323
  loot_items)
195
324
  dt = (time.perf_counter() - start_time)
325
+ if unknown_items:
326
+ click.echo(f"{Fore.RED}Could not find {len(unknown_items):,} items.{Style.RESET_ALL}")
327
+ click.echo(f"\t-> {Fore.RED}{f'{Style.RESET_ALL},{Fore.RED}'.join(unknown_items)}{Style.RESET_ALL}")
196
328
  click.echo(f"{Fore.GREEN}\tParsed loot statistics in {dt:.2f} seconds.{Style.RESET_ALL}")
197
329
  finally:
198
330
  conn.commit()
@@ -261,6 +261,8 @@ class Creature(abc.Row, abc.Parseable, table=schema.Creature):
261
261
  The creature's bestiary level, from 'Trivial' to 'Hard'
262
262
  bestiary_occurrence: :class:`str`
263
263
  The creature's bestiary occurrence, from 'Common' to 'Very Rare'.
264
+ bosstiary_class: :class:`str`
265
+ The creature's bosstiary class, if applicable.
264
266
  hitpoints: :class:`int`
265
267
  The creature's hitpoints, may be `None` if unknown.
266
268
  experience: :class:`int`
@@ -336,6 +338,7 @@ class Creature(abc.Row, abc.Parseable, table=schema.Creature):
336
338
  "creatureclass": ("creature_class", str.strip),
337
339
  "bestiaryclass": ("bestiary_class", str.strip),
338
340
  "bestiarylevel": ("bestiary_level", str.strip),
341
+ "bosstiaryclass": ("bosstiary_class", str.strip),
339
342
  "occurrence": ("bestiary_occurrence", str.strip),
340
343
  "primarytype": ("creature_type", str.strip),
341
344
  "secondarytype": ("type_secondary", str.strip),
@@ -384,6 +387,7 @@ class Creature(abc.Row, abc.Parseable, table=schema.Creature):
384
387
  "bestiary_level",
385
388
  "bestiary_class",
386
389
  "bestiary_occurrence",
390
+ "bosstiary_class",
387
391
  "hitpoints",
388
392
  "experience",
389
393
  "armor",
@@ -123,6 +123,8 @@ class Imbuement(abc.Row, abc.Parseable, table=schema.Imbuement):
123
123
  The tier of the imbuement.
124
124
  type: :class:`str`
125
125
  The imbuement's type.
126
+ category: :class:`str`
127
+ The imbuement's category.
126
128
  effect: :class:`str`
127
129
  The effect given by the imbuement.
128
130
  slots: :class:`str`
@@ -141,6 +143,7 @@ class Imbuement(abc.Row, abc.Parseable, table=schema.Imbuement):
141
143
  "name": ("name", str.strip),
142
144
  "prefix": ("tier", str.strip),
143
145
  "type": ("type", str.strip),
146
+ "category": ("category", str.strip),
144
147
  "effect": ("effect", parse_effect),
145
148
  "implemented": ("version", str.strip),
146
149
  "slots": ("slots", parse_slots),
@@ -155,6 +158,7 @@ class Imbuement(abc.Row, abc.Parseable, table=schema.Imbuement):
155
158
  "name",
156
159
  "tier",
157
160
  "type",
161
+ "category",
158
162
  "effect",
159
163
  "slots",
160
164
  "version",
@@ -282,15 +282,18 @@ class Item(abc.Row, abc.Parseable, table=schema.Item):
282
282
  if attribute in item._raw_attributes and item._raw_attributes[attribute]:
283
283
  item.attributes.append(ItemAttribute(item_id=item.article_id, name=name,
284
284
  value=item._raw_attributes[attribute]))
285
- item._parse_attributes()
286
- item._parse_resists()
287
- vocations = item._raw_attributes.get('vocrequired')
288
- if vocations and "none" not in vocations.lower():
289
- vocation = vocations.replace('and', '+').replace(',', '+').replace(' ', '')
290
- item.attributes.append(ItemAttribute(item_id=item.article_id, name="vocation", value=vocation))
291
- item._parse_sounds()
292
- item._parse_store_value()
293
- return item
285
+ try:
286
+ item._parse_attributes()
287
+ item._parse_resists()
288
+ vocations = item._raw_attributes.get('vocrequired')
289
+ if vocations and "none" not in vocations.lower():
290
+ vocation = vocations.replace('and', '+').replace(',', '+').replace(' ', '')
291
+ item.attributes.append(ItemAttribute(item_id=item.article_id, name="vocation", value=vocation))
292
+ item._parse_sounds()
293
+ item._parse_store_value()
294
+ return item
295
+ except Exception:
296
+ return None
294
297
 
295
298
  def _parse_attributes(self):
296
299
  if "attrib" not in self._raw_attributes:
@@ -600,7 +603,7 @@ class ItemAttribute(abc.Row, table=schema.ItemAttribute):
600
603
  "unshootable": "unshootable",
601
604
  "blocks_path": "blockspath",
602
605
  "walkable": "walkable",
603
- "walking_speed": "walkingspeed",
606
+ "tile_friction": "walkingspeed",
604
607
  "map_color": "mapcolor",
605
608
  "upgrade_classification": "upgradeclass"
606
609
  }
@@ -164,6 +164,8 @@ class Npc(abc.Row, abc.Parseable, table=schema.Npc):
164
164
  The jobs of the NPC.
165
165
  location: :class:`str`
166
166
  The location of the NPC.
167
+ subarea: :class:`str`
168
+ A finer location of the NPC.
167
169
  city: :class:`str`
168
170
  The nearest city to where the NPC is located.
169
171
  x: :class:`int`
@@ -198,6 +200,7 @@ class Npc(abc.Row, abc.Parseable, table=schema.Npc):
198
200
  "jobs",
199
201
  "location",
200
202
  "city",
203
+ "subarea",
201
204
  "x",
202
205
  "y",
203
206
  "z",
@@ -219,6 +222,7 @@ class Npc(abc.Row, abc.Parseable, table=schema.Npc):
219
222
  "location": ("location", clean_links),
220
223
  "gender": ("gender", str.strip),
221
224
  "city": ("city", str.strip),
225
+ "subarea": ("subarea", str.strip),
222
226
  "posx": ("x", convert_tibiawiki_position),
223
227
  "posy": ("y", convert_tibiawiki_position),
224
228
  "posz": ("z", int),
@@ -359,10 +363,10 @@ class Npc(abc.Row, abc.Parseable, table=schema.Npc):
359
363
 
360
364
  def insert(self, c):
361
365
  super().insert(c)
362
- for offer in getattr(self, "buy_offers", []):
363
- offer.insert(c)
364
- for offer in getattr(self, "sell_offers", []):
365
- offer.insert(c)
366
+ # for offer in getattr(self, "buy_offers", []):
367
+ # offer.insert(c)
368
+ # for offer in getattr(self, "sell_offers", []):
369
+ # offer.insert(c)
366
370
  for spell in getattr(self, "teaches", []):
367
371
  spell.insert(c)
368
372
  for destination in getattr(self, "destinations", []):
@@ -63,6 +63,7 @@ class Creature(Table):
63
63
  bestiary_class = Column(Text, index=True)
64
64
  bestiary_level = Column(Text, index=True)
65
65
  bestiary_occurrence = Column(Text, index=True)
66
+ bosstiary_class = Column(Text, index=True)
66
67
  runs_at = Column(Integer)
67
68
  summon_cost = Column(Integer)
68
69
  convince_cost = Column(Integer)
@@ -220,6 +221,7 @@ class Imbuement(Table):
220
221
  name = Column(Text, unique=True, index=True)
221
222
  tier = Column(Text, nullable=False)
222
223
  type = Column(Text, nullable=False)
224
+ category = Column(Text, nullable=False)
223
225
  effect = Column(Text)
224
226
  slots = Column(Text)
225
227
  version = Column(Text, index=True)
@@ -290,6 +292,7 @@ class Npc(Table):
290
292
  name = Column(Text, no_case=True, index=True)
291
293
  gender = Column(Text, index=True)
292
294
  city = Column(Text, index=True)
295
+ subarea = Column(Text)
293
296
  location = Column(Text)
294
297
  version = Column(Text, index=True)
295
298
  x = Column(Integer)
@@ -188,6 +188,7 @@ def parse_date(value):
188
188
 
189
189
  - June 28, 2019
190
190
  - Aug 21, 2014
191
+ - May 14, 2024 17:45
191
192
 
192
193
  Parameters
193
194
  ----------
@@ -200,11 +201,22 @@ def parse_date(value):
200
201
  The date represented by the string.
201
202
  """
202
203
  value = value.strip()
203
- try:
204
- dt = datetime.datetime.strptime(value, "%B %d, %Y")
205
- except ValueError:
206
- dt = datetime.datetime.strptime(value, "%b %d, %Y")
207
- return dt.date().isoformat()
204
+ date_formats = [
205
+ "%B %d, %Y",
206
+ "%b %d, %Y",
207
+ "%Y",
208
+ "%B %d, %Y %H:%M",
209
+ "%b %d, %Y %H:%M",
210
+ "%Y %H:%M",
211
+ ]
212
+ for date_format in date_formats:
213
+ try:
214
+ dt = datetime.datetime.strptime(value, date_format)
215
+ return dt.date().isoformat()
216
+ except ValueError:
217
+ continue
218
+
219
+ raise ValueError(f"Date format for value '{value}' not recognized")
208
220
 
209
221
 
210
222
  def parse_float(value, default=0.0):
@@ -297,7 +309,10 @@ def _parse_loot_entry(entry):
297
309
  key = subarg[0].strip()
298
310
  value = subarg[1].strip()
299
311
  entry[key] = value
300
- return entry
312
+ if "item" in entry:
313
+ return entry
314
+ else:
315
+ return None
301
316
 
302
317
 
303
318
  def parse_min_max(value):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tibiawikisql
3
- Version: 6.1.0
3
+ Version: 6.2.1
4
4
  Summary: Python script that generates a SQLite database from TibiaWiki articles
5
5
  Home-page: https://github.com/Galarzaa90/tibiawiki-sql
6
6
  Author: Galarzaa90
@@ -2,6 +2,7 @@ requests
2
2
  click
3
3
  colorama
4
4
  mwparserfromhell
5
+ lupa
5
6
 
6
7
  [docs]
7
8
  Sphinx
File without changes
File without changes
File without changes
File without changes
File without changes