From e9d7b3ff282571247fa4e9b2d36b0179c0ccb0f1 Mon Sep 17 00:00:00 2001 From: TheNexusAvenger <13441476+TheNexusAvenger@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:37:16 -0400 Subject: [PATCH 1/3] Create networker import command. --- .../commands/import_networkers_xml.py | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 mln/management/commands/import_networkers_xml.py diff --git a/mln/management/commands/import_networkers_xml.py b/mln/management/commands/import_networkers_xml.py new file mode 100644 index 0000000..df0abbf --- /dev/null +++ b/mln/management/commands/import_networkers_xml.py @@ -0,0 +1,180 @@ +import xml.etree.ElementTree as et + +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand + +from mln.models.dynamic import Friendship, FriendshipStatus, Profile, InventoryStack +from mln.models.dynamic.module import Module +from mln.models.dynamic.module_settings import ModuleSaveNetworkerPic, ModuleSaveNetworkerText, ModuleSetupTrade, ModuleSaveGeneric +from mln.models.static import Color, ItemInfo, ModuleSkin, MessageBody, NetworkerFriendshipCondition, MessageTemplate, MessageTemplateAttachment, NetworkerReply + +module_name_to_type = { + "banner": 43889, + "text": 43890, + "sticker": 56868, + "loop": 52701, + "trade": 43884, + "unknown": 0, +} +message_type_to_body_id = { + "helprequest": 45222, + "stickerrequest": 46023, + "looprequest": 45590, + "traderequest": 46024, +} + + +class Command(BaseCommand): + help = "Import networks to the DB from an XML file." + + def __init__(self): + super().__init__() + self.created_objects = {} + + def _store_object(self, table_object): + table = type(table_object) + if table not in self.created_objects.keys(): + self.created_objects[table] = [] + self.created_objects[table].append(table_object) + return table_object + + def add_arguments(self, parser): + parser.add_argument("path") + + def handle(self, *args, **options): + xml = et.parse(options["path"]) + + for networker in xml.findall("networker"): + if not User.objects.filter(username=networker.get("name")).exists(): + User.objects.create(username=networker.get("name")) + user = User.objects.filter(username=networker.get("name")).first() + + background_color_id = 1 + column_color_id = 1 + skin_id = None + networker_page_data = networker.find("page") + if networker_page_data is not None: + background_color_id = int(networker_page_data.get("backgroundColor", "1")) + column_color_id = int(networker_page_data.get("columnColor", "1")) + skin_id = networker_page_data.get("skin") + + profile = Profile.objects.filter(user=user).first() + profile.is_networker = True + profile.avatar = networker.get("avatar") + profile.rank = int(networker.get("rank")) + profile.page_color = Color.objects.filter(id=background_color_id).first() + profile.page_column_color_id = column_color_id + if skin_id is not None: + profile.page_skin = ItemInfo.objects.filter(id=int(skin_id)).first() + profile.save() + + for friend_data in networker.findall("friends/networker"): + friend_networker = User.objects.filter(username=friend_data.get("name")).first() + if friend_networker is not None: + self._store_object(Friendship(from_user=user, to_user=friend_networker, status=FriendshipStatus.FRIEND)) + self._store_object(Friendship(from_user=friend_networker, to_user=user, status=FriendshipStatus.FRIEND)) + + for module_data in networker.findall("modules/module"): + module_type = module_data.get("type", "unknown").lower() + module_id = int(module_data.get("id", module_name_to_type[module_type])) + module_item = ItemInfo.objects.filter(id=module_id).first() + + pos_x = int(module_data.get("x")) + pos_y = int(module_data.get("y")) + module = Module.objects.filter(owner=user, pos_x=pos_x, pos_y=pos_y).first() + if module is not None and module.item != module_item: + module.delete() + module = None + if module is None: + module = Module.objects.create(item=module_item, owner=user, pos_x=pos_x, pos_y=pos_y, is_setup=True) + + module_skin = None + module_color = None + if module_data.get("skin") is not None: + module_skin = ModuleSkin.objects.filter(id=int(module_data.get("skin"))).first() + if module_data.get("color") is not None: + module_color = Color.objects.filter(id=int(module_data.get("color"))).first() + if module_color is not None or module_skin is not None: + module_save_generic = ModuleSaveGeneric.objects.filter(module=module).first() + if module_save_generic is None: + module_save_generic = ModuleSaveGeneric.objects.create(module=module, skin=module_skin, color=module_color) + module_save_generic.skin = module_skin + module_save_generic.color = module_color + module_save_generic.save() + + if module_type == "banner": + picture = ItemInfo.objects.filter(id=int(module_data.get("bannerId"))).first() + module_picture = ModuleSaveNetworkerPic.objects.filter(module=module).first() + if module_picture is None: + module_picture = ModuleSaveNetworkerPic.objects.create(module=module, picture=picture) + module_picture.picture = picture + module_picture.save() + elif module_type == "text": + text = module_data.get("text").replace("\\n", "\n") + module_text = ModuleSaveNetworkerText.objects.filter(module=module).first() + if module_text is None: + module_text = ModuleSaveNetworkerText.objects.create(module=module, text=text) + module_text.text = text + module_text.save() + elif module_type == "sticker" or module_type == "loop" or module_type == "trade": + give_item = ItemInfo.objects.filter(id=int(module_data.get("giveId"))).first() + request_item = ItemInfo.objects.filter(id=int(module_data.get("requestId"))).first() + module_trade = ModuleSetupTrade.objects.filter(module=module).first() + if module_trade is None: + module_trade = ModuleSetupTrade.objects.create(module=module, give_item=give_item, give_qty=int(module_data.get("giveQuantity", "1")), request_item=request_item, request_qty=int(module_data.get("requestQuantity"))) + module_trade.give_item = give_item + module_trade.give_qty = int(module_data.get("giveQuantity", "1")) + module_trade.request_item = request_item + module_trade.request_qty = int(module_data.get("requestQuantity")) + module_trade.save() + + friendship_data = networker.find("friendship") + if friendship_data is not None: + condition_item = None + if friendship_data.get("requiredItem"): + condition_item = ItemInfo.objects.filter(id=int(friendship_data.get("id"))).first() + acceptBody = MessageBody.objects.filter(id=int(friendship_data.get("acceptMessageId", "1"))).first() + declineBody = MessageBody.objects.filter(id=int(friendship_data.get("declineMessageId", "1"))).first() + self._store_object(NetworkerFriendshipCondition(networker=user, condition=condition_item, success_body=acceptBody, failure_body=declineBody)) + + for item_message_trigger_data in networker.findall("messageTriggers/item"): + trigger_attachment = ItemInfo.objects.filter(id=int(item_message_trigger_data.get("id"))).first() + if NetworkerReply.objects.filter(networker=user, trigger_attachment=trigger_attachment).exists(): + continue + + message_body = MessageBody.objects.filter(id=item_message_trigger_data.get("responseId")).first() + message_template = MessageTemplate.objects.create(body=message_body) + attachment_data = item_message_trigger_data.find("attachment") + if attachment_data is not None: + attachment_item = ItemInfo.objects.filter(id=int(attachment_data.get("id"))).first() + self._store_object(MessageTemplateAttachment(template=message_template, item=attachment_item, qty=int(attachment_data.get("quantity", "1")))) + + self._store_object(NetworkerReply(template=message_template, networker=user, trigger_attachment=trigger_attachment)) + + for item_message_trigger_data in networker.findall("messageTriggers/message"): + trigger_body_id = item_message_trigger_data.get("id", "0") + if item_message_trigger_data.get("type") is not None: + trigger_body_id = message_type_to_body_id[item_message_trigger_data.get("type").lower()] + + trigger_body = MessageBody.objects.filter(id=int(trigger_body_id)).first() + if NetworkerReply.objects.filter(networker=user, trigger_body=trigger_body).exists(): + continue + + message_body = MessageBody.objects.filter(id=item_message_trigger_data.get("responseId")).first() + message_template = MessageTemplate.objects.create(body=message_body) + attachment_data = item_message_trigger_data.find("attachment") + if attachment_data is not None: + attachment_item = ItemInfo.objects.filter(id=int(attachment_data.get("id"))).first() + self._store_object(MessageTemplateAttachment(template=message_template, item=attachment_item, qty=int(attachment_data.get("quantity", "1")))) + + self._store_object(NetworkerReply(template=message_template, networker=user, trigger_body=trigger_body)) + + for badge_data in networker.findall("badges/badge"): + badge_item = ItemInfo.objects.filter(id=int(badge_data.get("id"))).first() + self._store_object(InventoryStack(owner=user, item=badge_item, qty=1)) + + print("Imported " + networker.get("name")) + + for key, value in self.created_objects.items(): + key.objects.bulk_create(value, ignore_conflicts=True) + print("Import complete.") \ No newline at end of file From d19417cea08823f1e9e546552c937fded548c771 Mon Sep 17 00:00:00 2001 From: TheNexusAvenger <13441476+TheNexusAvenger@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:41:00 -0400 Subject: [PATCH 2/3] Add note about networker importer. Move implemented features. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cc93f0c..5b1a225 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Initialize the database by running `python manage.py migrate`. This will create Included in the content files should be an XML file called `editorial-redux-v5.xml`. Run `python manage.py import_mln_xml `, where is the path to this file. This will import the original MLN data. -Note: The editorial XML unfortunately doesn't contain any information about networkers. This means that the server won't have any networkers, even after importing the MLN data. We're trying to piece together networker information ourselves, using the MLN wiki and screenshots of pages. However this information is likely incomplete, so while we may be able to reconstruct networkers to the point where you can achieve all ranks, we probably won't be able to get the layout of the modules on their pages completely correct. Once we've completed piecing together the networker data we'll also make this data available to you if you want to host your own server. +Note: The editorial XML unfortunately doesn't contain any information about networkers. This means that the server won't have any networkers, even after importing the MLN data. We're trying to piece together networker information ourselves, using the MLN wiki and screenshots of pages. However this information is likely incomplete, so while we may be able to reconstruct networkers to the point where you can achieve all ranks, we probably won't be able to get the layout of the modules on their pages completely correct. Networks can be imported from XML files using `python manage.py import_networkers_xml `, but these XML files are not provided with the content files. ## Run the server @@ -48,6 +48,8 @@ Do *not* use this way of running the server in production (if you actually want * Setup & Teardown * Voting & Execution * Arcade + * Random items sometimes sent to friends when you click on modules + * Items guests can receive in "battle" modules or similar * Avatar & About me * Badges * Page skins & colors @@ -55,12 +57,10 @@ Do *not* use this way of running the server in production (if you actually want * Gallery & Factory * Creation lab * Networker friendship conditions +* Networker mail replies ### Not yet implemented -* Networker mail replies -* Random items sometimes sent to friends when you click on modules -* Items guests can receive in "battle" modules or similar * Module-dependent sticker backgrounds * Statistics * Module stats From 5371157ec87d98df0c92bf09dfc5c96757be3d2b Mon Sep 17 00:00:00 2001 From: TheNexusAvenger <13441476+TheNexusAvenger@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:40:29 -0400 Subject: [PATCH 3/3] Create schema document. --- NetworkersSchema.md | 120 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 NetworkersSchema.md diff --git a/NetworkersSchema.md b/NetworkersSchema.md new file mode 100644 index 0000000..672634c --- /dev/null +++ b/NetworkersSchema.md @@ -0,0 +1,120 @@ +Networkers can be imported using the `import_networkers_xml` command. However, XML files with networkers are not provided with the content files. The command accepts custom XML files that follow the schema described below. + +# Schema +- Networkers are imported as long as they are a list of `` in the root tag ``. +- Each `` can have the following attributes: + - `name` (required) - Name of the networker. + - `avatar` (required) - String representation of the avatar of the network. For PNGs stored in the content files, use `png`. + - `rank` (required) - Rank of the networker. + - `hidden` can be included for hidden networkers, but is not used currently. +- Each `networker` can have the following optional children: + - `` - Describes the theming of the page. There are 3 optional attributes: + - `backgroundColor` - Color of the background if there is no skin. Must be a valid color id (1-9, 16-25, 32-40). 1 is default. + - `columnColor` - Color of the columns if there is no skin. Must be a valid color id (1-9, 16-25, 32-40). 1 is default. + - `skin` - Skin for the page that overrides background and color columns. Must be a valid item id. No value is used if not provided. + - `` - Specifies how the networker handles friendship requests. + - Can have the following attributes: + - `acceptMessageId` (required) - Message body id when the friendship is accepted. + - `declineMessageId` (required if the child `` exists) - Message body id when the friendship is declined. + - Can have the optional child `` where ID is the item id of an item the user must have to accept the friendship. + - `` - List of `` where NAME is the `name` of the networker to be friends with. Friends are bidirectional, but should be defined in both networkers since they are only created when the friended networker is already imported. + - `` - List of ``, which each containing the following parameters: + - `type` (required if `id` is not present) - Type of the module (`Banner`, `Text`, `Sticker`, `Loop`, or `Trade`). It will specify the module if `id` is not present and handle other database entries for the module. + - `id` (required if `type` is not present) - Id of the module to use. It will override the version based on the `type` (if present). + - `x` (required) - X coordinate (column) of the module. Must be 0, 1, or 2. + - `y` (required) - Y coordinate (Row) of the module. Must be 0, 1, 2, or 3. + - `color` (optional) - Color id of the module. Must be a valid color id (1-9, 16-25, 32-40). + - `bannerId` (required if `type` is `Banner`) - Id of the banner to display. + - `text` (required if `type` is `Text`) - Text to display in the box. + - `giveId` (required if `type` is `Sticker`, `Loop`, or `Trade`) - Id of the item the module gives to the user. + - `giveQuantity` (optional) - Quantity of the item the module gives to the user. Only relevant for the `type` being `Trade`. Defaults to 1. + - `requestId` (required if `type` is `Sticker`, `Loop`, or `Trade`) - Id of the item the module takes from the user. + - `requestQuantity` (optional) - Quantity of the item the module tales from the user. Only relevant for the `type` being `Sticker`, `Loop`, or `Trade`. Defaults to 1. + - `` - List of 2 potential children: + - `` - Triggers a response from the networker when an item is sent, regardless of message body. Must contain the attributes `id` for the id of the item and `responseId` for the id of the response body to send. + - `` - Triggers a response from the networker when a message body is sent. Must contain the attribute `responseId` for the id of the response body to send. To specify the required message body from the user, either the message body id must be given as the attribute `id`, or a helper `type` attribute to reference the message body (must be `HelpRequest`, `StickerRequest`, `LoopRequest`, or `TradeRequest`). + - For both `` and ``, an `` child can be added to specify an attachment. The attribute `id` is required for the id of the item to send, with an optional attribute `quantity` for how many to send (defaults to 1). + - `` is unused but is planned for handling when a user obtains an item. List of 2 potential children: + - `` - Triggers the networker to send a message when the user receives an item with the id from the attribute `itemId` for the first time. The attribute `messageId` is required for the message body id to send. + - `` - Triggers the networker to send a message when the user receives an item with the id from the attribute `itemId` after first time. The attribute `messageId` is required for the message body id to send. + - For both `` and ``, an `` child can be added to specify an attachment. The attribute `id` is required for the id of the item to send, with an optional attribute `quantity` for how many to send (defaults to 1). + +# Examples +```xml + + + + +``` \ No newline at end of file