From c1414269080ade0f5b8747a62663201373db23f7 Mon Sep 17 00:00:00 2001 From: DonAlex <7777993+develroo@users.noreply.github.com> Date: Mon, 12 May 2025 21:15:20 +0100 Subject: [PATCH] First push of updated NPC-Gen code --- Generator.py | 169 ++++++++++---------------- __pycache__/Generator.cpython-313.pyc | Bin 0 -> 6298 bytes npc-gen-final.py | 80 ++++++++++++ npc-gen.py | 67 ++++++++++ requirements.txt | 9 +- 5 files changed, 219 insertions(+), 106 deletions(-) create mode 100644 __pycache__/Generator.cpython-313.pyc create mode 100644 npc-gen-final.py create mode 100644 npc-gen.py diff --git a/Generator.py b/Generator.py index 581b392..d52b139 100644 --- a/Generator.py +++ b/Generator.py @@ -4,156 +4,115 @@ from dotenv import load_dotenv import json import sys import random +from faker import Faker +fake = Faker() class NpcGenerator: """ - This class is made to create random NPCs into a specific kanka campaign - """ - - apiToken = None - apiBaseUrl = None - headers = None - campaign_id = None - dataPath = None - - """ - When instancing the class, the variables from the .env files are loaded and the following class variables are set: - apiToken, dataPath, apiBaseUrl and headers + Generates random NPCs and creates them in a specified Kanka campaign. """ def __init__(self): - load_dotenv() - self.apiToken = os.getenv("APITOKEN") + + self.apiToken = f"Bearer {os.getenv('APITOKEN')}" self.dataPath = os.getenv("DATA_PATH") self.apiBaseUrl = os.getenv("API_URL") self.headers = { - 'Authorization': self.apiToken, + 'Authorization': f"{self.apiToken}", 'Accept': 'application/json', 'Content-Type': 'application/json' } + self.campaign_id = None def get_campaigns(self): - """ - This method will get a list of all the campaigns created on the account belonging to the owner of the Token - via a HTTP get request and returns a JSON - :return: JSON - """ - campaigns = requests.get("{}campaigns".format(self.apiBaseUrl), headers=self.headers) - return json.loads(campaigns.content) + response = requests.get(f"{self.apiBaseUrl}campaigns", headers=self.headers) + response.raise_for_status() + return response.json() - def set_campaign_id(self, campaign_name): - """ - This method will parse the JSON generated by get_campaigns and set the campaignId with the id of the campaign - specified in the campaign_name parameter. If no campaign can be found with that name, it will raise an error - :type campaign_name: str - """ + def set_campaign_id(self, campaign_name: str): campaigns = self.get_campaigns() - - for campaign in campaigns['data']: - if campaign['name'] == campaign_name: - print("Found the campaign that you are looking for with id:{}".format(campaign['id'])) + for campaign in campaigns.get('data', []): + if campaign.get('name') == campaign_name: + print(f"Found the campaign that you are looking for with id: {campaign['id']}") self.campaign_id = campaign['id'] + return + sys.exit("ERROR: No campaign with that name found!") - if self.campaign_id is None: - sys.exit("ERROR: No campaign with that name found!") + def load_character_details(self) -> dict: + def load_json(filename): + with open(os.path.join(self.dataPath, filename), 'r', encoding='utf-8') as f: + return json.load(f) - def load_charater_details(self): - """ - This method will load the three specified files from the location specified in dataPath and returns a dict - containing json objects - @:returns data: dict - """ - appearance_options = json.load(open('{}appearance.json'.format(self.dataPath))) - personality_options = json.load(open('{}personalities.json'.format(self.dataPath))) - title_options = json.load(open('{}titles.json'.format(self.dataPath))) - - data = { - 'appearance_options': appearance_options, - 'personality_options': personality_options, - 'title_options': title_options + return { + 'appearance_options': load_json('appearance.json'), + 'personality_options': load_json('personalities.json'), + 'title_options': load_json('titles.json') } - return data +# def generate_npc_name(self, sex: str) -> str: +# option = "boy_names" if sex == "Male" else "girl_names" +# url = f"http://names.drycodes.com/1?nameOptions={option}&format=text&separator=space" +# response = requests.get(url) +# response.raise_for_status() +# return response.text.strip() - def genrate_npc_name(self, sex): - """ - Will get a name via http request from drycodes.com based on the sex specified - :type sex: str - :return name: str - """ - if sex is "Male": - get_male_name = requests.get( - 'http://names.drycodes.com/1?nameOptions=boy_names&format=text&separator=space') - name = get_male_name.text + + def generate_npc_name(self, sex: str) -> str: + if sex == "Male": + return fake.name_male() else: - get_female_name = requests.get( - 'http://names.drycodes.com/1?nameOptions=girl_names&format=text&separator=space') - name = get_female_name.text + return fake.name_female() - return name - def generate_npcs(self, times): - """ - Will generate a list of dicts, each dict representing a character. The character details are populated - from the data retrieved by load_character_details. The number of characters is decided by the times parameter - :type times: int - :return npcs: list - """ + def generate_npcs(self, count: int) -> list: + data = self.load_character_details() npcs = [] - data = self.load_charater_details() - - for x in range(times): + for _ in range(count): sex = random.choice(['Male', 'Female']) - name = self.genrate_npc_name(sex) + name = self.generate_npc_name(sex) - character = { + npc = { 'name': name, 'title': random.choice(data['title_options']['data']), - 'age': str(random.randrange(14, 100)), + 'age': str(random.randint(14, 99)), 'sex': sex, 'type': 'random_npc', - 'is_private': 'true', + 'is_private': True, 'personality_name': ['Goals', 'Fears'], - 'personality_entry': [random.choice(data['personality_options']['goals']), - random.choice(data['personality_options']['fears'])], + 'personality_entry': [ + random.choice(data['personality_options']['goals']), + random.choice(data['personality_options']['fears']) + ], 'appearance_name': ['Hair', 'Eyes', 'Height', 'Marks', 'Body Type'], - 'appearance_entry': [random.choice(data['appearance_options']['hair_type']), - random.choice(data['appearance_options']['eyes']), - str(round(random.uniform(4.1, 6.9), 1)) + " feet", - random.choice(data['appearance_options']['marks']), - random.choice(data['appearance_options']['body_type'])] + 'appearance_entry': [ + random.choice(data['appearance_options']['hair_type']), + random.choice(data['appearance_options']['eyes']), + f"{round(random.uniform(4.1, 6.9), 1)} feet", + random.choice(data['appearance_options']['marks']), + random.choice(data['appearance_options']['body_type']) + ] } - - npcs.append(character) + npcs.append(npc) return npcs - def create_npcs(self, campaign_name=str, times=1): - """ - Will search for the specified campaign, generate the npcs and then create then on kanka via http post requests. - Please note that kanka has a limit of 30 calls per minute at this time - :type times: int - :type campaign_name: str - """ + def create_npcs(self, campaign_name: str, count: int = 1): self.set_campaign_id(campaign_name) - - npcs = self.generate_npcs(times) + npcs = self.generate_npcs(count) for npc in npcs: - - print('Posting character to kanka') + print(f"Posting character '{npc['name']}' to Kanka") response = requests.post( - "{}campaigns/{}/characters".format(self.apiBaseUrl, self.campaign_id), - data=json.dumps(npc), - headers=self.headers) + f"{self.apiBaseUrl}campaigns/{self.campaign_id}/characters", + json=npc, # modern requests usage + headers=self.headers + ) - if response.status_code is 201: - print("Character named {} was created successfully!".format(npc['name'])) + if response.status_code == 201: + print(f"✅ Character '{npc['name']}' created successfully.") else: - print( - "Character was not created because kanka threw an error, got status code {} with error {} ".format( - response.status_code, response.text)) + print(f"❌ Failed to create character '{npc['name']}': {response.status_code} - {response.text}") diff --git a/__pycache__/Generator.cpython-313.pyc b/__pycache__/Generator.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..216e156438d99548a45799e819edecdb9c968812 GIT binary patch literal 6298 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 Jq77Z${{g{}V~GF& literal 0 HcmV?d00001 diff --git a/npc-gen-final.py b/npc-gen-final.py new file mode 100644 index 0000000..1f90bf8 --- /dev/null +++ b/npc-gen-final.py @@ -0,0 +1,80 @@ + +#!/usr/bin/env python3 + +import argparse +import os +import sys +from dotenv import load_dotenv + +load_dotenv() + +from Generator import NpcGenerator + + +def validate_api_token(): + token = os.getenv("APITOKEN") + if not token: + sys.exit("❌ Error: API token is missing. Make sure APITOKEN= is set in your .env file.") + token = token.strip('"').strip("'") + os.environ["APITOKEN"] = token + return token + + +def select_campaign(generator): + campaigns = generator.get_campaigns().get("data", []) + if not campaigns: + sys.exit("❌ No campaigns found.") + + print("\nAvailable campaigns:") + for idx, campaign in enumerate(campaigns): + print(f" [{idx}] {campaign['name']} (ID: {campaign['id']})") + print(" [x] Exit") + + while True: + choice = input("\nEnter the number of the campaign to use (or 'x' to cancel): ").strip().lower() + if choice == 'x': + print("Exiting.") + sys.exit(0) + try: + index = int(choice) + if 0 <= index < len(campaigns): + return campaigns[index]['name'] + except ValueError: + pass + print("Invalid choice. Try again.") + + +def main(): + parser = argparse.ArgumentParser(description="Generate random NPCs in a Kanka campaign.") + parser.add_argument("--host", help="Set API base URL (e.g., https://kanka.example.com/api/1.0/)") + parser.add_argument("--campaign", help="Specify campaign name directly") + parser.add_argument("--count", type=int, help="Number of NPCs to generate", default=None) + + args = parser.parse_args() + + # Normalize host input if provided + if args.host: + host = args.host.strip() + if not host.startswith("http://") and not host.startswith("https://"): + host = "https://" + host + if not host.endswith("/api/1.0/"): + host = host.rstrip("/") + "/api/1.0/" + os.environ["API_URL"] = host + + validate_api_token() + generator = NpcGenerator() + + campaign_name = args.campaign or select_campaign(generator) + + count = args.count + if count is None: + try: + count = int(input("Enter number of NPCs to generate: ")) + except ValueError: + sys.exit("❌ Invalid number entered.") + + generator.create_npcs(campaign_name, count) + + +if __name__ == "__main__": + main() diff --git a/npc-gen.py b/npc-gen.py new file mode 100644 index 0000000..da6827b --- /dev/null +++ b/npc-gen.py @@ -0,0 +1,67 @@ + +#!/usr/bin/env python3 + +import argparse +import os +import sys +from Generator import NpcGenerator +from dotenv import load_dotenv +load_dotenv() + +def validate_api_token(): + token = os.getenv("APITOKEN") + if not token or len(token) < 30: + sys.exit("❌ Error: API token is missing or looks invalid. Make sure APITOKEN= is set in your .env file.") + return token + + +def select_campaign(generator): + campaigns = generator.get_campaigns().get("data", []) + if not campaigns: + sys.exit("❌ No campaigns found.") + + print("\nAvailable campaigns:") + for idx, campaign in enumerate(campaigns): + print(f" [{idx}] {campaign['name']} (ID: {campaign['id']})") + + while True: + try: + choice = int(input("\nEnter the number of the campaign to use: ")) + if 0 <= choice < len(campaigns): + return campaigns[choice]['name'] + except ValueError: + pass + print("Invalid choice. Try again.") + + +def main(): + parser = argparse.ArgumentParser(description="Generate random NPCs in a Kanka campaign.") + parser.add_argument("--host", help="Set API base URL (e.g., https://kanka.example.com/api/1.0/)") + parser.add_argument("--campaign", help="Specify campaign name directly") + parser.add_argument("--count", type=int, help="Number of NPCs to generate", default=None) + + args = parser.parse_args() + + # Set host override if provided + if args.host: + os.environ["API_URL"] = args.host + + validate_api_token() + generator = NpcGenerator() + + # Determine campaign name + campaign_name = args.campaign or select_campaign(generator) + + # Determine NPC count + count = args.count + if count is None: + try: + count = int(input("Enter number of NPCs to generate: ")) + except ValueError: + sys.exit("❌ Invalid number entered.") + + generator.create_npcs(campaign_name, count) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt index 1c49fe1..ed05127 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,9 @@ +certifi==2025.4.26 +chardet==3.0.4 +charset-normalizer==3.4.2 +Faker==37.1.0 +idna==2.10 python-dotenv==0.12.0 -requests==2.23.0 +requests==2.32.3 +tzdata==2025.2 +urllib3==1.26.20