# Copyright 2021 Allan Galarza
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import html
import re
import sqlite3
from tibiawikisql import schema
from tibiawikisql.models import abc
from tibiawikisql.utils import clean_links, parse_boolean, parse_integer
link_pattern = re.compile(r'\[\[([^|\]]+)')
def parse_links(value):
"""Find all the links in a string and returns a list of them.
Parameters
----------
value: :class:`str`
A string containing links.
Returns
-------
list(:class:`str`):
The links found in the string.
"""
return list(link_pattern.findall(value))
[docs]class Quest(abc.Row, abc.Parseable, table=schema.Quest):
"""Represents a quest.
Attributes
----------
article_id: :class:`int`
The id of the containing article.
title: :class:`str`
The title of the containing article.
timestamp: :class:`int`
The last time the containing article was edited.
name: :class:`str`
The name of the quest.
location: :class:`str`
The location of the quest.
rookgaard: :class:`bool`
Whether this quest is in Rookgaard or not.
type: :class:`str`
The type of quest.
quest_log: :class:`bool`
Whether this quest is registered in the quest log or not.
legend: :class:`str`
The legend of the quest.
level_required: :class:`int`
The level required to finish the quest.
level_recommended: :class:`int`
The recommended level to finish the quest.
active_time: :class:`str`
Times of the year when this quest is active.
estimated_time: :class:`str`:
Estimated time to finish this quest.
status: :class:`str`
The status of this quest in the game.
version: :class:`str`
The client version where this outfit was first implemented.
dangers: list of :class:`QuestDanger`
Creatures found in the quest.
rewards: list of :class:`QuestReward`
Items rewarded in the quest.
"""
__slots__ = (
"article_id",
"title",
"timestamp",
"name",
"location",
"rookgaard",
"type",
"quest_log",
"legend",
"level_required",
"level_recommended",
"active_time",
"estimated_time",
"version",
"dangers",
"rewards",
"status",
)
_map = {
"name": ("name", html.unescape),
"location": ("location", clean_links),
"rookgaardquest": ("rookgaard", parse_boolean),
"type": ("type", str.strip),
"log": ("quest_log", parse_boolean),
"legend": ("legend", clean_links),
"lvl": ("level_required", parse_integer),
"lvlrec": ("level_recommended", parse_integer),
"time": ("active_time", str.strip),
"timealloc": ("estimated_time", str.strip),
"premium": ("premium", parse_boolean),
"implemented": ("version", str.strip),
"status": ("status", str.lower),
}
_template = "Infobox_Quest"
def __init__(self, **kwargs):
super().__init__(**kwargs)
[docs] @classmethod
def from_article(cls, article):
quest = super().from_article(article)
if quest is None:
return quest
if "reward" in quest._raw_attributes:
rewards = parse_links(quest._raw_attributes["reward"])
quest.rewards = []
for reward in rewards:
quest.rewards.append(QuestReward(quest_id=quest.article_id, item_title=reward.strip()))
if "dangers" in quest._raw_attributes:
dangers = parse_links(quest._raw_attributes["dangers"])
quest.dangers = []
for danger in dangers:
quest.dangers.append(QuestDanger(quest_id=quest.article_id, creature_title=danger.strip()))
return quest
[docs] def insert(self, c):
super().insert(c)
for reward in getattr(self, "rewards", []):
reward.insert(c)
for danger in getattr(self, "dangers", []):
danger.insert(c)
[docs] @classmethod
def get_by_field(cls, c, field, value, use_like=False):
quest = super().get_by_field(c, field, value, use_like)
if quest is None:
return None
quest.dangers = QuestDanger.search(c, "quest_id", quest.article_id)
quest.rewards = QuestReward.search(c, "quest_id", quest.article_id)
return quest
[docs]class QuestReward(abc.Row, table=schema.QuestReward):
"""Represents an item obtained in the quest.
Attributes
----------
quest_id: :class:`int`
The article id of the quest.
quest_title: :class:`str`
The title of the quest.
item_id: :class:`int`
The article id of the rewarded item.
item_title: :class:`str`
The title of the rewarded item.
"""
__slots__ = (
"quest_id",
"quest_title",
"item_id",
"item_title",
)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.quest_title = kwargs.get("quest_title")
self.item_title = kwargs.get("item_title")
[docs] def insert(self, c):
if getattr(self, "item_id", None):
super().insert(c)
return
try:
c.execute(f"""INSERT INTO {self.table.__tablename__}(quest_id, item_id)
VALUES(?, (SELECT article_id FROM item WHERE title = ?))""",
(self.quest_id, self.item_title))
except sqlite3.IntegrityError:
pass
@classmethod
def _is_column(cls, name):
return name in cls.__slots__
@classmethod
def _get_base_query(cls):
return f"""SELECT {cls.table.__tablename__}.*, item.title as item_title, quest.title as quest_title
FROM {cls.table.__tablename__}
LEFT JOIN item ON item.article_id = item_id
LEFT JOIN quest ON quest.article_id = quest_id"""
[docs]class QuestDanger(abc.Row, table=schema.QuestDanger):
"""Represents a creature found in the quest.
Attributes
----------
quest_id: :class:`int`
The article id of the quest.
quest_title: :class:`str`
The title of the quest.
creature_id: :class:`int`
The article id of the found creature.
creature_title: :class:`str`
The title of the found creature.
"""
__slots__ = (
"quest_id",
"quest_title",
"creature_id",
"creature_title",
)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.quest_title = kwargs.get("quest_title")
self.creature_title = kwargs.get("creature_title")
[docs] def insert(self, c):
if getattr(self, "creature_id", None):
super().insert(c)
return
try:
c.execute(f"""INSERT INTO {self.table.__tablename__}(quest_id, creature_id)
VALUES(?, (SELECT article_id FROM creature WHERE title = ?))""",
(self.quest_id, self.creature_title))
except sqlite3.IntegrityError:
pass
@classmethod
def _is_column(cls, name):
return name in cls.__slots__
@classmethod
def _get_base_query(cls):
return f"""SELECT {cls.table.__tablename__}.*, creature.title as creature_title, quest.title as quest_title
FROM {cls.table.__tablename__}
LEFT JOIN creature ON creature.article_id = creature_id
LEFT JOIN quest ON quest.article_id = quest_id"""