#=============================================================================== # Copyright (C) 2010 Diego Duclos # # This file is part of pyfa. # # pyfa is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # pyfa is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with pyfa. If not, see . #=============================================================================== import re import threading import wx import Queue import eos.db import eos.types from service.settings import SettingsProvider, ProxySettings try: from collections import OrderedDict except ImportError: from gui.utils.compat import OrderedDict # Event which tells threads dependent on Market that it's initialized mktRdy = threading.Event() class ShipBrowserWorkerThread(threading.Thread): def run(self): self.queue = Queue.Queue() self.cache = {} # Wait for full market initialization (otherwise there's high risky # this thread will attempt to init Market which is already being inited) mktRdy.wait(5) self.processRequests() def processRequests(self): queue = self.queue cache = self.cache sMarket = Market.getInstance() while True: try: callback, id = queue.get() set = cache.get(id) if set is None: set = sMarket.getShipList(id) cache[id] = set wx.CallAfter(callback, (id, set)) except: pass finally: try: queue.task_done() except: pass class PriceWorkerThread(threading.Thread): def run(self): self.queue = Queue.Queue() self.processUpdates() def processUpdates(self): queue = self.queue while True: # Grab our data callback, requests = queue.get() # Grab prices, this is the time-consuming part if len(requests) > 0: proxy = ProxySettings.getInstance().getProxySettings() if proxy is not None: proxy = "{0}:{1}".format(*proxy) eos.types.Price.fetchPrices(requests, proxy=proxy) wx.CallAfter(callback) queue.task_done() def trigger(self, prices, callbacks): self.queue.put((callbacks, prices)) class SearchWorkerThread(threading.Thread): def run(self): self.cv = threading.Condition() self.searchRequest = None self.processSearches() def processSearches(self): cv = self.cv while True: cv.acquire() while self.searchRequest is None: cv.wait() request, callback = self.searchRequest self.searchRequest = None cv.release() sMarket = Market.getInstance() # Rely on category data provided by eos as we don't hardcode them much in service filter = eos.types.Category.name.in_(sMarket.SEARCH_CATEGORIES) results = eos.db.searchItems(request, where=filter, join=(eos.types.Item.group, eos.types.Group.category), eager=("icon", "group.category", "metaGroup", "metaGroup.parent")) items = set() # Return only published items, consult with Market service this time for item in results: if sMarket.getPublicityByItem(item): items.add(item) wx.CallAfter(callback, items) def scheduleSearch(self, text, callback): self.cv.acquire() self.searchRequest = (text, callback) self.cv.notify() self.cv.release() class Market(): instance = None def __init__(self): self.priceCache = {} #Init recently used module storage serviceMarketRecentlyUsedModules = {"pyfaMarketRecentlyUsedModules": []} self.serviceMarketRecentlyUsedModules = SettingsProvider.getInstance().getSettings("pyfaMarketRecentlyUsedModules", serviceMarketRecentlyUsedModules) # Start price fetcher self.priceWorkerThread = PriceWorkerThread() self.priceWorkerThread.daemon = True self.priceWorkerThread.start() # Thread which handles search self.searchWorkerThread = SearchWorkerThread() self.searchWorkerThread.daemon = True self.searchWorkerThread.start() # Ship browser helper thread self.shipBrowserWorkerThread = ShipBrowserWorkerThread() self.shipBrowserWorkerThread.daemon = True self.shipBrowserWorkerThread.start() # Items' group overrides self.customGroups = set() # Limited edition ships self.les_grp = eos.types.Group() self.les_grp.ID = -1 self.les_grp.name = "Limited Issue Ships" self.les_grp.published = True ships = self.getCategory("Ship") self.les_grp.category = ships self.les_grp.categoryID = ships.ID self.les_grp.description = "" self.les_grp.icon = None self.ITEMS_FORCEGROUP = { "Opux Luxury Yacht": self.les_grp, # One of those is wedding present at CCP fanfest, another was hijacked from ISD guy during an event "Silver Magnate": self.les_grp, # Amarr Championship prize "Gold Magnate": self.les_grp, # Amarr Championship prize "Armageddon Imperial Issue": self.les_grp, # Amarr Championship prize "Apocalypse Imperial Issue": self.les_grp, # Amarr Championship prize "Guardian-Vexor": self.les_grp, # Illegal rewards for the Gallente Frontier Tour Lines event arc "Megathron Federate Issue": self.les_grp, # Reward during Crielere event "Raven State Issue": self.les_grp, # AT4 prize "Tempest Tribal Issue": self.les_grp, # AT4 prize "Apotheosis": self.les_grp, # 5th EVE anniversary present "Zephyr": self.les_grp, # 2010 new year gift "Primae": self.les_grp, # Promotion of planetary interaction "Freki": self.les_grp, # AT7 prize "Mimir": self.les_grp, # AT7 prize "Utu": self.les_grp, # AT8 prize "Adrestia": self.les_grp, # AT8 prize "Echelon": self.les_grp, # 2011 new year gift "Malice": self.les_grp, # AT9 prize "Vangel": self.les_grp, # AT9 prize "Cambion": self.les_grp, # AT10 prize "Etana": self.les_grp, # AT10 prize "Chremoas": self.les_grp, # AT11 prize :( "Moracha": self.les_grp, # AT11 prize "Interbus Shuttle": self.les_grp, "Leopard": self.les_grp, "Stratios Emergency Responder": self.les_grp } self.ITEMS_FORCEGROUP_R = self.__makeRevDict(self.ITEMS_FORCEGROUP) self.les_grp.addItems = list(self.getItem(itmn) for itmn in self.ITEMS_FORCEGROUP_R[self.les_grp]) self.customGroups.add(self.les_grp) # List of items which are forcibly published or hidden self.ITEMS_FORCEPUBLISHED = { "Data Subverter I": False, # Not used in EVE, probably will appear with Dust link "Ghost Heavy Missile": False, # Missile used by Sansha "QA Cross Protocol Analyzer": False, # QA modules used by CCP internally "QA Damage Module": False, "QA ECCM": False, "QA Immunity Module": False, "QA Multiship Module - 10 Players": False, "QA Multiship Module - 20 Players": False, "QA Multiship Module - 40 Players": False, "QA Multiship Module - 5 Players": False, "QA Remote Armor Repair System - 5 Players": False, "QA Shield Transporter - 5 Players": False, "Aliastra Catalyst": False, # Vanity "Inner Zone Shipping Catalyst": False, # Vanity "Intaki Syndicate Catalyst": False, # Vanity "InterBus Catalyst": False, # Vanity "Quafe Catalyst": False, # Vanity "Nefantar Thrasher": False, # Vanity "Sarum Magnate": False, # Vanity "Sukuuvestaa Heron": False, # Vanity "Inner Zone Shipping Imicus": False, # Vanity "Vherokior Probe": False, # Vanity "Miasmos Quafe Ultra Edition": False, # Vanity "Miasmos Quafe Ultramarine Edition": False, # Vanity "Miasmos Amastris Edition": False, # Vanity "Goru's Shuttle": False, # Vanity "Guristas Shuttle": False, # Vanity "Tash-Murkon Magnate": False, # Vanity "Scorpion Ishukone Watch": False } # Vanity # List of groups which are forcibly published self.GROUPS_FORCEPUBLISHED = { "Prototype Exploration Ship": False } # We moved the only ship from this group to other group anyway # Dictionary of items with forced meta groups, uses following format: # Item name: (metagroup name, parent type name) self.ITEMS_FORCEDMETAGROUP = { "'Habitat' Miner I": ("Storyline", "Miner I"), "'Wild' Miner I": ("Storyline", "Miner I"), "Medium Nano Armor Repair Unit I": ("Tech I", "Medium Armor Repairer I"), "Large 'Reprieve' Vestment Reconstructer I": ("Storyline", "Large Armor Repairer I"), "Khanid Navy Torpedo Launcher": ("Faction", "Torpedo Launcher I"), "Dark Blood Tracking Disruptor": ("Faction", "Tracking Disruptor I"), "True Sansha Tracking Disruptor": ("Faction", "Tracking Disruptor I"), "Shadow Serpentis Remote Sensor Dampener": ("Faction", "Remote Sensor Dampener I") } # Parent type name: set(item names) self.ITEMS_FORCEDMETAGROUP_R = {} for item, value in self.ITEMS_FORCEDMETAGROUP.items(): parent = value[1] if not parent in self.ITEMS_FORCEDMETAGROUP_R: self.ITEMS_FORCEDMETAGROUP_R[parent] = set() self.ITEMS_FORCEDMETAGROUP_R[parent].add(item) # Dictionary of items with forced market group (service assumes they have no # market group assigned in db, otherwise they'll appear in both original and forced groups) self.ITEMS_FORCEDMARKETGROUP = { "'Alpha' Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners "'Codex' Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners "'Daemon' Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners "'Libram' Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners "Advanced Cerebral Accelerator": 977, # Implants & Boosters > Booster "Civilian Damage Control": 760, # Ship Equipment > Civilian Modules "Civilian EM Ward Field": 760, # Ship Equipment > Civilian Modules "Civilian Explosive Deflection Field": 760, # Ship Equipment > Civilian Modules "Civilian Hobgoblin": 837, # Drones > Combat Drones > Light Scout Drones "Civilian Kinetic Deflection Field": 760, # Ship Equipment > Civilian Modules "Civilian Light Missile Launcher": 760, # Ship Equipment > Civilian Modules "Civilian Scourge Light Missile": 920, # Ammunition & Charges > Missiles > Light Missiles > Standard Light Missiles "Civilian Small Remote Armor Repairer": 760, # Ship Equipment > Civilian Modules "Civilian Small Remote Shield Booster": 760, # Ship Equipment > Civilian Modules "Civilian Stasis Webifier": 760, # Ship Equipment > Civilian Modules "Civilian Thermic Dissipation Field": 760, # Ship Equipment > Civilian Modules "Civilian Warp Disruptor": 760, # Ship Equipment > Civilian Modules "Hardwiring - Zainou 'Sharpshooter' ZMX10": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 "Hardwiring - Zainou 'Sharpshooter' ZMX100": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 "Hardwiring - Zainou 'Sharpshooter' ZMX1000": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 "Hardwiring - Zainou 'Sharpshooter' ZMX11": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 "Hardwiring - Zainou 'Sharpshooter' ZMX110": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 "Hardwiring - Zainou 'Sharpshooter' ZMX1100": 1493, # Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06 "Nugoehuvi Synth Blue Pill Booster": 977, # Implants & Boosters > Booster "Prototype Cerebral Accelerator": 977, # Implants & Boosters > Booster "Prototype Iris Probe Launcher": 712, # Ship Equipment > Turrets & Bays > Scan Probe Launchers "Shadow": 1310, # Drones > Combat Drones > Fighter Bombers "Sleeper Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners "Standard Cerebral Accelerator": 977, # Implants & Boosters > Booster "Talocan Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners "Terran Data Analyzer I": 714, # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners "Tetrimon Data Analyzer I": 714 } # Ship Equipment > Electronics and Sensor Upgrades > Scanners > Data and Composition Scanners self.ITEMS_FORCEDMARKETGROUP_R = self.__makeRevDict(self.ITEMS_FORCEDMARKETGROUP) # Misc definitions # 0 is for items w/o meta group self.META_MAP = OrderedDict([("normal", frozenset((0, 1, 2, 14))), ("faction", frozenset((4, 3))), ("complex", frozenset((6,))), ("officer", frozenset((5,)))]) self.SEARCH_CATEGORIES = ("Drone", "Module", "Subsystem", "Charge", "Implant") self.ROOT_MARKET_GROUPS = (9, # Modules 1111, # Rigs 157, # Drones 11, # Ammo 1112, # Subsystems 24) # Implants & Boosters # Tell other threads that Market is at their service mktRdy.set() @classmethod def getInstance(cls): if cls.instance == None: cls.instance = Market() return cls.instance def __makeRevDict(self, orig): """Creates reverse dictionary""" rev = {} for item, value in orig.items(): if not value in rev: rev[value] = set() rev[value].add(item) return rev def getItem(self, identity, *args, **kwargs): """Get item by its ID or name""" if isinstance(identity, eos.types.Item): item = identity elif isinstance(identity, (int, basestring)): item = eos.db.getItem(identity, *args, **kwargs) elif isinstance(identity, float): id = int(identity) item = eos.db.getItem(id, *args, **kwargs) else: raise TypeError("Need Item object, integer, float or string as argument") return item def getGroup(self, identity, *args, **kwargs): """Get group by its ID or name""" if isinstance(identity, eos.types.Group): return identity elif isinstance(identity, (int, float, basestring)): if isinstance(identity, float): identity = int(identity) # Check custom groups for cgrp in self.customGroups: # During first comparison we need exact int, not float for matching if cgrp.ID == identity or cgrp.name == identity: # Return first match return cgrp # Return eos group if everything else returned nothing return eos.db.getGroup(identity, *args, **kwargs) else: raise TypeError("Need Group object, integer, float or string as argument") def getCategory(self, identity, *args, **kwargs): """Get category by its ID or name""" if isinstance(identity, eos.types.Category): category = identity elif isinstance(identity, (int, basestring)): category = eos.db.getCategory(identity, *args, **kwargs) elif isinstance(identity, float): id = int(identity) category = eos.db.getCategory(id, *args, **kwargs) else: raise TypeError("Need Category object, integer, float or string as argument") return category def getMetaGroup(self, identity, *args, **kwargs): """Get meta group by its ID or name""" if isinstance(identity, eos.types.MetaGroup): metaGroup = identity elif isinstance(identity, (int, basestring)): metaGroup = eos.db.getMetaGroup(identity, *args, **kwargs) elif isinstance(identity, float): id = int(identity) metaGroup = eos.db.getMetaGroup(id, *args, **kwargs) else: raise TypeError("Need MetaGroup object, integer, float or string as argument") return metaGroup def getMarketGroup(self, identity, *args, **kwargs): """Get market group by its ID""" if isinstance(identity, eos.types.MarketGroup): marketGroup = identity elif isinstance(identity, (int, float)): id = int(identity) marketGroup = eos.db.getMarketGroup(id, *args, **kwargs) else: raise TypeError("Need MarketGroup object, integer or float as argument") return marketGroup def getGroupByItem(self, item): """Get group by item""" if item.name in self.ITEMS_FORCEGROUP: group = self.ITEMS_FORCEGROUP[item.name] else: group = item.group return group def getCategoryByItem(self, item): """Get category by item""" grp = self.getGroupByItem(item) cat = grp.category return cat def getMetaGroupByItem(self, item): """Get meta group by item""" # Check if item is in forced metagroup map if item.name in self.ITEMS_FORCEDMETAGROUP: # Create meta group from scratch metaGroup = eos.types.MetaType() # Get meta group info object based on meta group name metaGroupInfo = self.getMetaGroup(self.ITEMS_FORCEDMETAGROUP[item.name][0]) # Get parent item based on its name parent = self.getItem(self.ITEMS_FORCEDMETAGROUP[item.name][1]) # Assign all required for metaGroup variables metaGroup.info = metaGroupInfo metaGroup.items = item metaGroup.parent = parent metaGroup.metaGroupID = metaGroupInfo.ID metaGroup.parentTypeID = parent.ID metaGroup.typeID = item.ID # If no forced meta group is provided, try to use item's # meta group if any else: metaGroup = item.metaGroup return metaGroup def getMetaGroupIdByItem(self, item, fallback=0): """Get meta group ID by item""" id = getattr(self.getMetaGroupByItem(item), "ID", fallback) return id def getMarketGroupByItem(self, item, parentcheck=True): """Get market group by item, its ID or name""" # Check if we force market group for given item if item.name in self.ITEMS_FORCEDMARKETGROUP: mgid = self.ITEMS_FORCEDMARKETGROUP[item.name] return self.getMarketGroup(mgid) # Check if item itself has market group elif item.marketGroupID: return item.marketGroup elif parentcheck: # If item doesn't have marketgroup, check if it has parent # item and use its market group parent = self.getParentItemByItem(item, selfparent=False) if parent: return parent.marketGroup else: return None else: return None def getParentItemByItem(self, item, selfparent=True): """Get parent item by item""" mg = self.getMetaGroupByItem(item) if mg: parent = mg.parent # Consider self as parent if item has no parent in database elif selfparent is True: parent = item else: parent = None return parent def getVariationsByItems(self, items, alreadyparent=False): """Get item variations by item, its ID or name""" # Set for IDs of parent items parents = set() # Set-container for variables variations = set() for item in items: # Get parent item if alreadyparent is False: parent = self.getParentItemByItem(item) else: parent = item # Combine both in the same set parents.add(parent) # Check for overrides and add them if any if parent.name in self.ITEMS_FORCEDMETAGROUP_R: for itmn in self.ITEMS_FORCEDMETAGROUP_R[parent.name]: variations.add(self.getItem(itmn)) # Add all parents to variations set variations.update(parents) # Add all variations of parents to the set parentids = tuple(item.ID for item in parents) variations.update(eos.db.getVariations(parentids)) return variations def getGroupsByCategory(self, cat): """Get groups from given category""" groups = set(filter(lambda grp: self.getPublicityByGroup(grp), cat.groups)) return groups def getMarketGroupChildren(self, mg): """Get the children marketGroups of marketGroup.""" children = set() for child in mg.children: children.add(child) return children def getItemsByGroup(self, group): """Get items assigned to group""" # Return only public items; also, filter out items # which were forcibly set to other groups groupItems = set(group.items) if hasattr(group, 'addItems'): groupItems.update(group.addItems) items = set(filter(lambda item: self.getPublicityByItem(item) and self.getGroupByItem(item) == group, groupItems)) return items def getItemsByMarketGroup(self, mg, vars=True): """Get items in the given market group""" result = set() # Get items from eos market group baseitms = set(mg.items) # Add hardcoded items to set if mg.ID in self.ITEMS_FORCEDMARKETGROUP_R: forceditms = set(self.getItem(itmn) for itmn in self.ITEMS_FORCEDMARKETGROUP_R[mg.ID]) baseitms.update(forceditms) if vars: parents = set() for item in baseitms: # Add one of the base market group items to result result.add(item) parent = self.getParentItemByItem(item, selfparent=False) # If item has no parent, it's base item (or at least should be) if parent is None: parents.add(item) # Fetch variations only for parent items variations = self.getVariationsByItems(parents, alreadyparent=True) for variation in variations: # Exclude items with their own explicitly defined market groups if self.getMarketGroupByItem(variation, parentcheck=False) is None: result.add(variation) else: result = baseitms # Get rid of unpublished items result = set(filter(lambda item: self.getPublicityByItem(item), result)) return result def marketGroupHasTypesCheck(self, mg): """If market group has any items, return true""" if mg and mg.ID in self.ITEMS_FORCEDMARKETGROUP_R: return True elif len(mg.items) > 0: return True else: return False def marketGroupValidityCheck(self, mg): """Check market group validity""" # The only known case when group can be invalid is # when it's declared to have types, but it doesn't contain anything if mg.hasTypes and not self.marketGroupHasTypesCheck(mg): return False else: return True def getIconByMarketGroup(self, mg): """Return icon associated to marketgroup""" if mg.icon: return mg.icon.iconFile else: while mg and not mg.hasTypes: mg = mg.parent if not mg: return "" elif self.marketGroupHasTypesCheck(mg): # Do not request variations to make process faster # Pick random item and use its icon items = self.getItemsByMarketGroup(mg, vars=False) try: item = items.pop() except KeyError: return "" return item.icon.iconFile if item.icon else "" elif self.getMarketGroupChildren(mg) > 0: kids = self.getMarketGroupChildren(mg) mktGroups = self.getIconByMarketGroup(kids) size = len(mktGroups) return mktGroups.pop() if size > 0 else "" else: return "" def getPublicityByItem(self, item): """Return if an item is published""" if item.name in self.ITEMS_FORCEPUBLISHED: pub = self.ITEMS_FORCEPUBLISHED[item.name] else: pub = item.published return pub def getPublicityByGroup(self, group): """Return if an group is published""" if group.name in self.GROUPS_FORCEPUBLISHED: pub = self.GROUPS_FORCEPUBLISHED[group.name] else: pub = group.published return pub def getMarketRoot(self): """ Get the root of the market tree. Returns a list, where each element is a tuple containing: the ID, the name and the icon of the group """ root = set() for id in self.ROOT_MARKET_GROUPS: mg = self.getMarketGroup(id, eager="icon") root.add(mg) return root def getShipRoot(self): cat = self.getCategory("Ship") root = set(self.getGroupsByCategory(cat)) return root def getShipList(self, grpid): """Get ships for given group id""" grp = self.getGroup(grpid, eager=("items", "items.group", "items.marketGroup")) ships = self.getItemsByGroup(grp) for ship in ships: ship.race return ships def getShipListDelayed(self, id, callback): """Background version of getShipList""" self.shipBrowserWorkerThread.queue.put((id, callback)) def searchShips(self, name): """Find ships according to given text pattern""" filter = eos.types.Category.name.in_(["Ship"]) results = eos.db.searchItems(name, where=filter, join=(eos.types.Item.group, eos.types.Group.category), eager=("icon", "group.category", "metaGroup", "metaGroup.parent")) ships = set() for item in results: if self.getPublicityByItem(item): ships.add(item) return ships def searchItems(self, name, callback): """Find items according to given text pattern""" self.searchWorkerThread.scheduleSearch(name, callback) def directAttrRequest(self, items, attribs): try: itemIDs = tuple(map(lambda i: i.ID, items)) except TypeError: itemIDs = (items.ID,) try: attrIDs = tuple(map(lambda i: i.ID, attribs)) except TypeError: attrIDs = (attribs.ID,) info = {} for itemID, typeID, val in eos.db.directAttributeRequest(itemIDs, attrIDs): info[itemID] = val return info def getImplantTree(self): """Return implant market group children""" img = self.getMarketGroup(27) return self.getMarketGroupChildren(img) def filterItemsByMeta(self, items, metas): """Filter items by meta lvl""" filtered = set(filter(lambda item: self.getMetaGroupIdByItem(item) in metas, items)) return filtered def getPriceNow(self, typeID): """Get price for provided typeID""" price = self.priceCache.get(typeID) if price is None: price = eos.db.getPrice(typeID) if price is None: price = eos.types.Price(typeID) eos.db.add(price) self.priceCache[typeID] = price return price def getPricesNow(self, typeIDs): """Return map of calls to get price against list of typeIDs""" return map(self.getPrice, typeIDs) def getPrices(self, typeIDs, callback): """Get prices for multiple typeIDs""" requests = [] for typeID in typeIDs: price = self.getPriceNow(typeID) requests.append(price) def cb(): try: callback(requests) except: pass eos.db.commit() self.priceWorkerThread.trigger(requests, cb) def getSystemWideEffects(self): """ Get dictionary with system-wide effects """ # Container for system-wide effects effects = {} # Expressions for matching when detecting effects we're looking for validgroups = ("Black Hole Effect Beacon", "Cataclysmic Variable Effect Beacon", "Magnetar Effect Beacon", "Pulsar Effect Beacon", "Red Giant Beacon", "Wolf Rayet Effect Beacon", "Incursion ship attributes effects") # Stuff we don't want to see in names garbages = ("Effect", "Beacon", "ship attributes effects") # Get group with all the system-wide beacons grp = self.getGroup("Effect Beacon") beacons = self.getItemsByGroup(grp) # Cycle through them for beacon in beacons: # Check if it belongs to any valid group for group in validgroups: # Check beginning of the name only if re.match(group, beacon.name): # Get full beacon name beaconname = beacon.name for garbage in garbages: beaconname = re.sub(garbage, "", beaconname) beaconname = re.sub(" {2,}", " ", beaconname).strip() # Get short name shortname = re.sub(group, "", beacon.name) for garbage in garbages: shortname = re.sub(garbage, "", shortname) shortname = re.sub(" {2,}", " ", shortname).strip() # Get group name groupname = group for garbage in garbages: groupname = re.sub(garbage, "", groupname) groupname = re.sub(" {2,}", " ", groupname).strip() # Add stuff to dictionary if not groupname in effects: effects[groupname] = set() effects[groupname].add((beacon, beaconname, shortname)) # Break loop on 1st result break return effects