#!/usr/bin/python

"""
File Name: chub_to_lorebook.py
Version: 1.1.0

Description: This script parses json chub files and converts to a NovelAI compatible Lorebook
Usage: python chub_to_lorebook.py <chub_json_file.json> [<user_name>]
Dependencies: This script requires Python 3.6 or later.

Latest Changes:
- Added automatic {{char}} substitution
- Added optional {{user}} substitution via a command line argument
"""
import json
import os
import re
import sys
import uuid

if len(sys.argv) < 2:
    print("Usage: python chub_to_lorebook.py <chub_json_file.json>")
    sys.exit(1)

source_file_path = sys.argv[1]
base_name, _ = os.path.splitext(source_file_path)
output_file_path = f"{base_name}.lorebook"

# Load the source JSON data
with open(source_file_path, 'r') as source_file:
    source_data = json.load(source_file).get("data")

# Setting user and char name
user_name = sys.argv[2] if len(sys.argv) > 2 else None
char_name = source_data.get("name")

# base format for the final output
transformed_data = {
    "lorebookVersion": 5,
    "entries": [],
    "settings": {"orderByKeyLocations": False},
    "categories": []
}

regex_single_asterisk = re.compile(r"(?<!\*)\*(?!\*)")
regex_user = re.compile(r"\{\{user\}\}")
regex_char = re.compile(r"\{\{char\}\}")

#
# get description
#
description_text = source_data.get("description", "")

# add personality to description if its present and not in the description
personality_text = source_data.get("personality")

if (personality_text and (personality_text not in description_text)):
    description_text += "\n" + personality_text

# substitute the user name if it was provided
if (user_name):
    description_text = regex_user.sub(user_name, description_text)

# substitute the char name
description_text = regex_char.sub(char_name, description_text)

description_entry = {
        "text": description_text,
        "displayName": char_name,
        "enabled": True,
        "keys": [char_name]
    }

transformed_data["entries"].append(description_entry)

#
# get credits
#
credits = {
        "text": "",
        "displayName": char_name + " Credits",
        "enabled": False,
    }
credits["text"] += "Author: " + source_data.get("creator")
credits["text"] += "\nImage: " + source_data.get("avatar")
credits["text"] += "\nLink: " + "http://www.chub.ai/characters/" + source_data.get("extensions").get("chub").get("full_path")
transformed_data["entries"].append(credits)

#
# create scenario category
#
scenarios_uuid = str(uuid.uuid4())
scenarios_category = {
        "name": char_name + " Scenarios",
        "id": scenarios_uuid,
        "enabled": False,
    }
transformed_data["categories"].append(scenarios_category)

#
# Prepare scenario numbering prefix
#
total_scenarios = len(source_data.get("alternate_greetings", [])) + 1
add_zero = total_scenarios > 9

#
# Get main scenario
#
scenario_text = regex_single_asterisk.sub("", source_data.get("first_mes", ""))
scenario_text = regex_char.sub(char_name, scenario_text)

# substitute the user name if it was provided
if (user_name):
    scenario_text = regex_user.sub(user_name, scenario_text)

scenario_entry = {
        "text": scenario_text,
        "displayName": ("0" if add_zero else "") + "1. Scenario",
        "enabled": False,
        "category": scenarios_uuid,
    }
transformed_data["entries"].append(scenario_entry)

#
# get mes_example (behavior) if provided
#
mes_example_text = source_data.get("mes_example", "")
if (mes_example_text):
    mes_example_text = regex_char.sub(char_name, mes_example_text)

    # substitute the user name if it was provided
    if (user_name):
        mes_example_text = regex_user.sub(user_name, mes_example_text)

    mes_example_text = f"{char_name} behavior example:\n{mes_example_text}"
    behavior_entry = {
            "text": regex_single_asterisk.sub("", mes_example_text),
            "displayName": char_name + " Scenario Behavior",
            "enabled": True,
        }
    transformed_data["entries"].append(behavior_entry)

#
# get alternative scnearios/greetings
#
scenario_num = 2
for entry in source_data.get("alternate_greetings", []):
    added_zero = "0" if add_zero and scenario_num < 10 else ""

    scenario_text = regex_single_asterisk.sub("", entry)

    # substitute the user name if it was provided
    if (user_name):
        scenario_text = regex_user.sub(user_name, scenario_text)

    # substitute the char name
    scenario_text = regex_char.sub(char_name, scenario_text)

    transformed_entry = {
        "text": scenario_text,
        "displayName": f"{added_zero}{scenario_num}. Scenario",
        "enabled": False,
        "category": scenarios_uuid,
    }
    transformed_data["entries"].append(transformed_entry)
    scenario_num += 1

#
# Write the transformed data to the new JSON file, named based on the input file
#
with open(output_file_path, 'w') as target_file:
    json.dump(transformed_data, target_file, indent=4)

print(f"Transformation complete. Data written to {output_file_path}")
Edit Report
Pub: 26 Feb 2024 20:12 UTC
Edit: 29 Feb 2024 23:22 UTC
Views: 249