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 0000000..216e156 Binary files /dev/null and b/__pycache__/Generator.cpython-313.pyc differ 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