Skip to content
Commits on Source (4)
  • Ken Kyger's avatar
    chore(meta): update compatibility range in module.json · 701ba8a1
    Ken Kyger authored
    - Added "maximum": "13" to the compatibility section of module.json, ensuring the module is recognized as compatible with Foundry VTT versions up to 13. This change enhances user clarity regarding supported versions.
    701ba8a1
  • Ken Kyger's avatar
    feat(module): enhance Alpha-5 module functionality and logging · dfa32e22
    Ken Kyger authored
    - Updated .gitignore to exclude development ZIP files.
    - Refactored Taskfile.yml to include new tasks for installing development versions and building ZIP files.
    - Enhanced logging in Alpha5 scripts by utilizing Alpha5Utils for consistent message formatting.
    - Added new settings and user management features in Alpha5Settings for better API integration.
    - Improved diagnostics and error handling across various components to provide clearer feedback to users.
    
    These changes improve the overall usability and maintainability of the Alpha-5 module, ensuring a smoother experience for developers and users alike.
    dfa32e22
  • Ken Kyger's avatar
    fix(logging): update world identifier logging to use world title · b6d60f12
    Ken Kyger authored
    - Changed logging statements in Alpha5 scripts to replace `game.world.id` with `game.world.title` for improved clarity in log messages.
    - Updated API endpoint construction to use the encoded world title instead of the world ID, ensuring consistency across the module.
    - Adjusted diagnostics to reflect the world title in relevant logs, enhancing the overall user experience and debugging capabilities.
    
    These changes enhance the clarity of log messages and ensure that the correct world identifier is used throughout the module.
    b6d60f12
  • semantic-release-bot's avatar
    chore(release): 1.7.0 [skip ci] · 8a3e65b7
    semantic-release-bot authored
    # [1.7.0](v1.6.3...v1.7.0) (2025-06-18)
    
    ### Bug Fixes
    
    * **logging:** update world identifier logging to use world title ([b6d60f12](b6d60f12))
    
    ### Features
    
    * **module:** enhance Alpha-5 module functionality and logging ([dfa32e22](dfa32e22))
    8a3e65b7
......@@ -77,6 +77,7 @@ typings/
# FoundryVTT module build artifacts
module.zip
module.json
alpha-5*.zip
# ignore kube configs
.kube*
......
# [1.7.0](https://gitlab.futurehax.com/foundryvtt/alpha-5-module/compare/v1.6.3...v1.7.0) (2025-06-18)
### Bug Fixes
* **logging:** update world identifier logging to use world title ([b6d60f1](https://gitlab.futurehax.com/foundryvtt/alpha-5-module/commit/b6d60f12b37649ac9cb0ebcb56111495c7f0a90a))
### Features
* **module:** enhance Alpha-5 module functionality and logging ([dfa32e2](https://gitlab.futurehax.com/foundryvtt/alpha-5-module/commit/dfa32e226d5caa12905d660d7cd73821097d43c5))
## [1.6.3](https://gitlab.futurehax.com/foundryvtt/alpha-5-module/compare/v1.6.2...v1.6.3) (2025-06-12)
......
......@@ -67,10 +67,58 @@ tasks:
desc: "Install directly into the Foundry World"
cmds:
# 192.168.86.27
- ssh marvin@192.168.86.27 rm -rf foundry12data/Data/modules/alpha-5/*
- ssh marvin@192.168.86.27 mkdir -p foundry12data/Data/modules/alpha-5
- ssh marvin@192.168.86.27 "source ~/.nvm/nvm.sh && rm -rf foundry12data/Data/modules/alpha-5/*"
- ssh marvin@192.168.86.27 "source ~/.nvm/nvm.sh && mkdir -p foundry12data/Data/modules/alpha-5"
- scp -r foundry_vtt/* marvin@192.168.86.27:foundry12data/Data/modules/alpha-5
install:foundry:dev:
silent: true
desc: "Install development version into Foundry (separate module)"
cmds:
- ./tasks/install-dev.sh
install:foundry:both:
silent: true
desc: "Install both production and development versions"
cmds:
- task: install:foundry
- task: install:foundry:dev
dev:build:
desc: "Build development module ZIP file"
cmds:
- |
echo "Building development module ZIP..."
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
# Copy module files
cp -r foundry_vtt/* "$TEMP_DIR/"
# Use development manifest
cp "$TEMP_DIR/module-dev.json" "$TEMP_DIR/module.json"
rm -f "$TEMP_DIR/module-dev.json"
# Get module ID from manifest
MODULE_ID=$(jq -r '.id' "$TEMP_DIR/module.json")
# Create zip
cd "$TEMP_DIR" && zip -r "$OLDPWD/${MODULE_ID}.zip" .
echo "✓ Created ${MODULE_ID}.zip"
dev:version:
desc: "Show current development and production versions"
cmds:
- echo "Production version:" && jq -r '.version' foundry_vtt/module.json
- echo "Development version:" && jq -r '.version' foundry_vtt/module-dev.json
- echo "Production ID:" && jq -r '.id' foundry_vtt/module.json
- echo "Development ID:" && jq -r '.id' foundry_vtt/module-dev.json
dev:sync-version:
desc: "Sync development version with production (adds -dev suffix)"
cmds:
- node tasks/sync-dev-version.js
# Release Tasks
release:
desc: "Create a new release using semantic-release (automated versioning)"
......
......@@ -17,6 +17,15 @@
"name": "Sync Interval",
"hint": "How often to sync players (in minutes)"
}
},
"table": {
"headers": {
"id": "ID",
"displayName": "Display Name",
"discordId": "Discord ID",
"tag": "Tag"
},
"noUsers": "No users found"
}
}
}
\ No newline at end of file
{
"id": "alpha-5-dev",
"title": "Alpha-5 (Development)",
"description": "Development version of Alpha-5 - Fetches and displays user data from Alpha-5",
"version": "1.6.3-dev",
"compatibility": {
"minimum": "12",
"verified": "12",
"maximum": "13"
},
"authors": [
{
"name": "r2DoesInc"
}
],
"esmodules": [
"scripts/main.js"
],
"styles": [
"styles/module.css"
],
"languages": [
{
"lang": "en",
"name": "English",
"path": "languages/en.json"
}
],
"url": "https://gitlab.futurehax.com/foundryvtt/alpha-5-module",
"manifest": "https://a5.futurehax.dev/foundry/module-dev.json",
"download": "https://a5.futurehax.dev/foundry/alpha-5-dev.zip"
}
\ No newline at end of file
......@@ -2,10 +2,11 @@
"id": "alpha-5",
"title": "Alpha-5",
"description": "Fetches and displays user data from Alpha-5",
"version": "1.6.3",
"version": "1.7.0",
"compatibility": {
"minimum": "12",
"verified": "12"
"verified": "12",
"maximum": "13"
},
"authors": [
{
......@@ -26,6 +27,6 @@
}
],
"url": "https://gitlab.futurehax.com/foundryvtt/alpha-5-module",
"manifest": "https://gitlab.futurehax.com/foundryvtt/alpha-5-module/-/raw/v1.6.3/foundry_vtt/module.json",
"download": "https://gitlab.futurehax.com/foundryvtt/alpha-5-module/-/archive/v1.6.3/alpha-5-module-v1.6.3.zip"
"manifest": "https://gitlab.futurehax.com/foundryvtt/alpha-5-module/-/raw/v1.7.0/foundry_vtt/module.json",
"download": "https://gitlab.futurehax.com/foundryvtt/alpha-5-module/-/archive/v1.7.0/alpha-5-module-v1.7.0.zip"
}
......@@ -3,12 +3,13 @@ import { Alpha5Welcome } from './welcome/Alpha5Welcome.js';
import { Alpha5PlayerManager } from './player/Alpha5PlayerManager.js';
import { Alpha5Settings } from './settings/Alpha5Settings.js';
import { Alpha5Diagnostics } from './api/Alpha5Diagnostics.js';
import { Alpha5Utils } from './api/Alpha5Utils.js';
export class Alpha5 {
static async syncPlayers() {
// Only log sync message if we're a GM
if (game.user.isGM) {
console.log(`Alpha-5 | Syncing players for world: ${game.world.id}...`);
console.log(Alpha5Utils.formatLogMessage(`Syncing players for world: ${game.world.title}...`));
}
// Only proceed with GM-specific operations if user is GM
......@@ -16,17 +17,17 @@ export class Alpha5 {
try {
const users = await Alpha5API.fetchUsers();
if (users.length === 0) {
console.log("Alpha-5 | No users returned from API - this may be normal if world is not registered or API is unavailable");
console.log(Alpha5Utils.formatLogMessage("No users returned from API - this may be normal if world is not registered or API is unavailable"));
return;
}
console.log(`Alpha-5 | Processing ${users.length} users from API`);
console.log(Alpha5Utils.formatLogMessage(`Processing ${users.length} users from API`));
for (const user of users) {
await Alpha5PlayerManager.createNewPlayer(user);
}
console.log("Alpha-5 | Player sync completed successfully");
console.log(Alpha5Utils.formatLogMessage("Player sync completed successfully"));
} catch (error) {
console.error("Alpha-5 | Failed to sync players:", error);
console.error(Alpha5Utils.formatLogMessage("Failed to sync players:"), error);
// Don't show error notification here as Alpha5API already handles it
}
}
......@@ -35,14 +36,14 @@ export class Alpha5 {
static async initialize() {
// Only log initialization message if we're a GM
if (game.user.isGM) {
console.log(`Alpha-5 | Module Initialized for world: ${game.world.id}`);
console.log(`Alpha-5 | API Endpoint: ${Alpha5API.API_ENDPOINT}`);
console.log(Alpha5Utils.formatLogMessage(`Module Initialized for world: ${game.world.title}`));
console.log(Alpha5Utils.formatLogMessage(`API Endpoint: ${Alpha5API.API_ENDPOINT}`));
// Test basic connectivity
const isConnected = await Alpha5API.testConnection();
if (!isConnected) {
console.warn("Alpha-5 | Cannot reach Alpha-5 service - functionality will be limited");
console.log("Alpha-5 | Run 'Alpha5.runDiagnostics()' in console for detailed troubleshooting");
console.warn(Alpha5Utils.formatLogMessage(`Cannot reach ${Alpha5Utils.getModuleName()} service - functionality will be limited`));
console.log(Alpha5Utils.formatLogMessage("Run 'Alpha5.runDiagnostics()' in console for detailed troubleshooting"));
}
}
......@@ -54,7 +55,7 @@ export class Alpha5 {
try {
await Alpha5Welcome.createWelcomeJournal();
} catch (error) {
console.error("Alpha-5 | Failed to create welcome journal:", error);
console.error(Alpha5Utils.formatLogMessage("Failed to create welcome journal:"), error);
}
}
......
import { Alpha5Utils } from "./Alpha5Utils.js";
export class Alpha5API {
static DEV_API_URL = "https://a5.futurehax.dev/api";
static PROD_API_URL = "https://alpha-5.app/api";
static DEFAULT_API_URL = this.PROD_API_URL;
static get DEFAULT_API_URL() {
return Alpha5Utils.getApiUrl();
}
static get API_ENDPOINT() {
const baseUrl = this.DEFAULT_API_URL;
// Note: Using world.id instead of deprecated world.name
// If the API truly requires the world name (not ID), this may need adjustment
// The endpoint path suggests it wants a name, but we're using ID to avoid deprecation warnings
const worldIdentifier = game.world.id;
return `${baseUrl}/public/worlds/${worldIdentifier}/users`;
const worldIdentifier = encodeURIComponent(game.world.title);
return `${baseUrl}/public/worlds/users/byName/${worldIdentifier}`;
}
static get API_KEY() {
return game.settings.get("alpha-5", "apiKey");
return game.settings.get(Alpha5Utils.getCurrentModuleId(), "apiKey");
}
static async fetchUsers() {
......@@ -21,12 +20,12 @@ export class Alpha5API {
// Check if API key is configured
const apiKey = this.API_KEY;
if (!apiKey) {
console.warn("Alpha-5 | No API key configured. Skipping user sync.");
ui.notifications.warn("Alpha-5 API key not configured. Please set it in module settings.");
console.warn(Alpha5Utils.formatLogMessage("No API key configured. Skipping user sync."));
ui.notifications.warn(`${Alpha5Utils.getModuleName()} API key not configured. Please set it in module settings.`);
return [];
}
console.log(`Alpha-5 | Attempting to fetch users from: ${this.API_ENDPOINT}`);
console.log(Alpha5Utils.formatLogMessage(`Attempting to fetch users from: ${this.API_ENDPOINT}`));
const response = await fetch(this.API_ENDPOINT, {
mode: "cors",
......@@ -38,13 +37,13 @@ export class Alpha5API {
if (!response.ok) {
if (response.status === 404) {
console.warn(`Alpha-5 | API endpoint not found: ${this.API_ENDPOINT}`);
console.warn(`Alpha-5 | World identifier '${game.world.id}' may not be registered with Alpha-5 service`);
ui.notifications.warn(`World '${game.world.id}' not found in Alpha-5. Please register your world first.`);
console.warn(Alpha5Utils.formatLogMessage(`API endpoint not found: ${this.API_ENDPOINT}`));
console.warn(Alpha5Utils.formatLogMessage(`World identifier '${game.world.title}' may not be registered with ${Alpha5Utils.getModuleName()} service`));
ui.notifications.warn(`World '${game.world.title}' not found in ${Alpha5Utils.getModuleName()}. Please register your world first.`);
return [];
} else if (response.status === 401 || response.status === 403) {
console.error("Alpha-5 | API key authentication failed");
ui.notifications.error("Alpha-5 API key is invalid. Please check your settings.");
console.error(Alpha5Utils.formatLogMessage("API key authentication failed"));
ui.notifications.error(`${Alpha5Utils.getModuleName()} API key is invalid. Please check your settings.`);
return [];
} else {
throw new Error(`HTTP error! status: ${response.status}`);
......@@ -52,15 +51,15 @@ export class Alpha5API {
}
const users = await response.json();
console.log(`Alpha-5 | Successfully fetched ${users.data?.length || 0} users`);
console.log(Alpha5Utils.formatLogMessage(`Successfully fetched ${users.data?.length || 0} users`));
return users.data || [];
} catch (error) {
if (error.name === 'TypeError' && error.message === 'Failed to fetch') {
console.error("Alpha-5 | Network error or CORS blocked:", error);
ui.notifications.error("Unable to connect to Alpha-5 service. Check console for details.");
console.error(Alpha5Utils.formatLogMessage("Network error or CORS blocked:"), error);
ui.notifications.error(`Unable to connect to ${Alpha5Utils.getModuleName()} service. Check console for details.`);
} else {
console.error("Alpha-5 | Error fetching users:", error);
ui.notifications.error("Failed to fetch Alpha-5 users data");
console.error(Alpha5Utils.formatLogMessage("Error fetching users:"), error);
ui.notifications.error(`Failed to fetch ${Alpha5Utils.getModuleName()} users data`);
}
return [];
}
......@@ -71,15 +70,16 @@ export class Alpha5API {
// Check if API key is configured
const apiKey = this.API_KEY;
if (!apiKey) {
console.warn("Alpha-5 | No API key configured");
ui.notifications.warn("Alpha-5 API key not configured. Please set it in module settings.");
console.warn(Alpha5Utils.formatLogMessage("No API key configured"));
ui.notifications.warn(`${Alpha5Utils.getModuleName()} API key not configured. Please set it in module settings.`);
return false;
}
const worldIdentifier = game.world.id;
const deleteEndpoint = `${this.DEFAULT_API_URL}/public/worlds/${worldIdentifier}/users/${userId}`;
const worldIdentifier = encodeURIComponent(game.world.title);
const encodedUserId = encodeURIComponent(userId);
const deleteEndpoint = `${this.DEFAULT_API_URL}/public/worlds/${worldIdentifier}/users/${encodedUserId}`;
console.log(`Alpha-5 | Attempting to delete user: ${userId}`);
console.log(Alpha5Utils.formatLogMessage(`Attempting to delete user: ${userId}`));
const response = await fetch(deleteEndpoint, {
method: "DELETE",
......@@ -92,43 +92,43 @@ export class Alpha5API {
if (!response.ok) {
if (response.status === 404) {
console.error(`Alpha-5 | User not found: ${userId}`);
ui.notifications.error("User not found in Alpha-5 service");
console.error(Alpha5Utils.formatLogMessage(`User not found: ${userId}`));
ui.notifications.error(`User not found in ${Alpha5Utils.getModuleName()} service`);
return false;
} else if (response.status === 401 || response.status === 403) {
console.error("Alpha-5 | API key authentication failed");
ui.notifications.error("Alpha-5 API key is invalid. Please check your settings.");
console.error(Alpha5Utils.formatLogMessage("API key authentication failed"));
ui.notifications.error(`${Alpha5Utils.getModuleName()} API key is invalid. Please check your settings.`);
return false;
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
console.log(`Alpha-5 | Successfully deleted user: ${userId}`);
ui.notifications.info("User deleted successfully from Alpha-5");
console.log(Alpha5Utils.formatLogMessage(`Successfully deleted user: ${userId}`));
ui.notifications.info(`User deleted successfully from ${Alpha5Utils.getModuleName()}`);
return true;
} catch (error) {
console.error("Alpha-5 | Error deleting user:", error);
ui.notifications.error("Failed to delete user from Alpha-5");
console.error(Alpha5Utils.formatLogMessage("Error deleting user:"), error);
ui.notifications.error(`Failed to delete user from ${Alpha5Utils.getModuleName()}`);
return false;
}
}
static async testConnection() {
try {
const baseUrl = this.DEFAULT_API_URL.replace('/api', '');
const baseUrl = Alpha5Utils.getBaseUrl();
const response = await fetch(baseUrl, { mode: 'no-cors' });
console.log("Alpha-5 | Base server reachable");
console.log(Alpha5Utils.formatLogMessage("Base server reachable"));
return true;
} catch (error) {
console.error("Alpha-5 | Base server unreachable:", error);
console.error(Alpha5Utils.formatLogMessage("Base server unreachable:"), error);
return false;
}
}
// Quick CORS test method
static async testCORSEndpoint() {
console.log("Testing CORS for Alpha-5 API endpoint...");
console.log(`Testing CORS for ${Alpha5Utils.getModuleName()} API endpoint...`);
try {
const response = await fetch(this.API_ENDPOINT, {
mode: "cors",
......
import { Alpha5Utils } from "./Alpha5Utils.js";
export class Alpha5Diagnostics {
static async runDiagnostics() {
console.log("=== Alpha-5 Diagnostics ===");
console.log(`=== ${Alpha5Utils.getModuleName()} Diagnostics ===`);
const results = {
worldId: game.world.id,
worldId: game.world.title,
apiKey: this.hasApiKey(),
baseServerReachable: false,
apiEndpointReachable: false,
......@@ -22,7 +24,10 @@ export class Alpha5Diagnostics {
// Test 2: Check base server connectivity
console.log("2. Testing base server connectivity...");
try {
const baseUrl = "https://a5.futurehax.dev";
const baseUrl = Alpha5Utils.getBaseUrl();
const environment = Alpha5Utils.isDevelopmentModule() ? 'development' : 'production';
console.log(` Using ${environment} server: ${baseUrl}`);
const response = await fetch(baseUrl, {
mode: 'no-cors',
method: 'HEAD'
......@@ -36,15 +41,16 @@ export class Alpha5Diagnostics {
// Test 3: Check API endpoint
console.log("3. Testing API endpoint...");
// Use the same endpoint structure as the main API
const worldIdentifier = game.world.id;
const apiEndpoint = `https://a5.futurehax.dev/api/public/worlds/byName/${worldIdentifier}/users`;
const apiBase = Alpha5Utils.getApiUrl();
const worldIdentifier = game.world.title;
const apiEndpoint = `${apiBase}/public/worlds/byName/${worldIdentifier}/users`;
console.log(` Endpoint: ${apiEndpoint}`);
try {
const response = await fetch(apiEndpoint, {
mode: 'cors',
method: 'HEAD',
headers: results.apiKey ? { 'X-API-Key': game.settings.get("alpha-5", "apiKey") } : {}
headers: results.apiKey ? { 'X-API-Key': game.settings.get(Alpha5Utils.getCurrentModuleId(), "apiKey") } : {}
});
if (response.ok) {
......@@ -54,7 +60,7 @@ export class Alpha5Diagnostics {
} else {
console.log(`✗ API endpoint returned status: ${response.status}`);
if (response.status === 404) {
console.log(" This likely means the world is not registered with Alpha-5");
console.log(` This likely means the world is not registered with ${Alpha5Utils.getModuleName()}`);
console.log(" Or the world identifier doesn't match what's registered");
}
}
......@@ -72,9 +78,13 @@ export class Alpha5Diagnostics {
// Test 4: Environment information
console.log("4. Environment information:");
const moduleInfo = Alpha5Utils.getModuleInfo();
console.log(` Module: ${moduleInfo.title} (${moduleInfo.id})`);
console.log(` Module Version: ${moduleInfo.version}`);
console.log(` Environment: ${moduleInfo.environment}`);
console.log(` Origin: ${window.location.origin}`);
console.log(` User Agent: ${navigator.userAgent}`);
console.log(` World ID: ${game.world.id}`);
console.log(` World ID: ${game.world.title}`);
console.log(` API Endpoint: ${apiEndpoint}`);
console.log(` Foundry Version: ${game.version}`);
......@@ -83,7 +93,7 @@ export class Alpha5Diagnostics {
}
static hasApiKey() {
const apiKey = game.settings.get("alpha-5", "apiKey");
const apiKey = game.settings.get(Alpha5Utils.getCurrentModuleId(), "apiKey");
return apiKey && apiKey.trim().length > 0;
}
......@@ -92,7 +102,7 @@ export class Alpha5Diagnostics {
let content = `
<div style="font-family: monospace; font-size: 12px;">
<h3>Alpha-5 Diagnostics Results</h3>
<h3>${Alpha5Utils.getModuleName()} Diagnostics Results</h3>
<p><strong>World ID:</strong> ${results.worldId}</p>
<p><strong>API Key:</strong> ${results.apiKey ? '✓ Configured' : '✗ Missing'}</p>
<p><strong>Server Reachable:</strong> ${results.baseServerReachable ? '✓ Yes' : '✗ No'}</p>
......@@ -102,16 +112,16 @@ export class Alpha5Diagnostics {
<p><strong>Common Solutions:</strong></p>
<ul style="text-align: left; margin-left: 20px;">
<li>If API key is missing: Configure it in Module Settings</li>
<li>If CORS error: Contact Alpha-5 admin to add your origin: <code>${window.location.origin}</code></li>
<li>If 404 error: Register your world with Alpha-5 service</li>
<li>If CORS error: Contact ${Alpha5Utils.getModuleName()} admin to add your origin: <code>${window.location.origin}</code></li>
<li>If 404 error: Register your world with ${Alpha5Utils.getModuleName()} service</li>
<li>Check browser console for detailed error messages</li>
<li>Verify world identifier matches what's registered with Alpha-5</li>
<li>Verify world identifier matches what's registered with ${Alpha5Utils.getModuleName()}</li>
</ul>
</div>
`;
new Dialog({
title: "Alpha-5 Diagnostics",
title: `${Alpha5Utils.getModuleName()} Diagnostics`,
content: content,
buttons: {
close: {
......
export class Alpha5Utils {
/**
* Detects which Alpha-5 module is currently running
* @returns {string} The module ID ('alpha-5' or 'alpha-5-dev')
*/
static getCurrentModuleId() {
// Check if game.modules is available
if (!game?.modules) {
console.warn("Alpha5Utils: game.modules not yet available");
return "alpha-5";
}
// Log all active modules for debugging
const activeModules = game.modules.filter(m => m.active).map(m => m.id);
const alpha5Module = game.modules.find(m =>
m.active && (m.id === "alpha-5" || m.id === "alpha-5-dev")
);
return alpha5Module?.id || "alpha-5";
}
/**
* Checks if the current module is the development version
* @returns {boolean} True if running the development module
*/
static isDevelopmentModule() {
return this.getCurrentModuleId() === "alpha-5-dev";
}
/**
* Gets the appropriate base URL for the current module
* @returns {string} The base URL (without /api)
*/
static getBaseUrl() {
return this.isDevelopmentModule()
? "https://a5.futurehax.dev"
: "https://alpha-5.app";
}
/**
* Gets the appropriate API URL for the current module
* @returns {string} The API base URL
*/
static getApiUrl() {
return `${this.getBaseUrl()}/api`;
}
/**
* Gets the module display name (without environment suffix)
* @returns {string} The base module name
*/
static getModuleName() {
// Check if game.modules is available
if (!game?.modules) {
return "Alpha-5";
}
const module = game.modules.get(this.getCurrentModuleId());
if (!module) return "Alpha-5";
// For dev module, strip the "(Development)" suffix
const title = module.title || "Alpha-5";
return title.replace(/\s*\(Development\)\s*$/, '').trim();
}
/**
* Gets the full module title (includes environment suffix for dev)
* @returns {string} The full module title
*/
static getModuleTitle() {
// Check if game.modules is available
if (!game?.modules) {
return "Alpha-5";
}
const module = game.modules.get(this.getCurrentModuleId());
return module?.title || "Alpha-5";
}
/**
* Gets a formatted string for console logging
* @param {string} message - The message to log
* @returns {string} Formatted message with module prefix
*/
static formatLogMessage(message) {
return `${this.getModuleName()} | ${message}`;
}
/**
* Gets module information for debugging
* @returns {object} Module information including ID, title, and environment
*/
static getModuleInfo() {
const moduleId = this.getCurrentModuleId();
const module = game.modules.get(moduleId);
return {
id: moduleId,
name: this.getModuleName(),
title: this.getModuleTitle(),
version: module?.version || "Unknown",
environment: this.isDevelopmentModule() ? "development" : "production",
baseUrl: this.getBaseUrl(),
apiUrl: this.getApiUrl()
};
}
}
\ No newline at end of file
import { Alpha5 } from './Alpha5.js';
import { Alpha5Welcome } from './welcome/Alpha5Welcome.js';
import { Alpha5Utils } from './api/Alpha5Utils.js';
// Immediate debug log to verify module loading
console.log("=== ALPHA-5 MODULE LOADING ===");
console.warn("Alpha-5 module main.js file is being executed");
// The Alpha5 class will handle all initialization and hooks
console.log('Alpha-5 Module | Loading...');
console.log("Alpha-5 Module | Loading...");
// Make Alpha5 available globally for debugging
window.Alpha5 = Alpha5;
......@@ -10,11 +15,12 @@ window.Alpha5 = Alpha5;
// Initialize the module when Foundry is ready
Hooks.once("ready", async () => {
try {
console.log(`${Alpha5Utils.getModuleTitle()} | Initializing...`);
await Alpha5.initialize();
console.log('Alpha-5 Module | Ready! Run "Alpha5.showDiagnostics()" in console for troubleshooting');
console.log(`${Alpha5Utils.getModuleTitle()} | Ready! Run "Alpha5.showDiagnostics()" in console for troubleshooting`);
} catch (error) {
console.error('Alpha-5 Module | Initialization failed:', error);
console.log('Alpha-5 Module | Run "Alpha5.runDiagnostics()" in console for troubleshooting');
console.error(`${Alpha5Utils.getModuleTitle()} | Initialization failed:`, error);
console.log(`${Alpha5Utils.getModuleTitle()} | Run "Alpha5.runDiagnostics()" in console for troubleshooting`);
}
});
......
import { Alpha5Utils } from '../api/Alpha5Utils.js';
export class Alpha5PlayerManager {
static async createNewPlayer(userData) {
try {
// Only GMs can create users
if (!game.user.isGM) {
console.warn("Alpha-5 | Only GMs can create new users");
console.warn(Alpha5Utils.formatLogMessage("Only GMs can create new users"));
return;
}
const existingUser = game.users.find(
(u) => u.getFlag("alpha-5", "discordId") === userData.discordId
(u) => u.getFlag(Alpha5Utils.getCurrentModuleId(), "discordId") === userData.discordId
);
if (existingUser) {
console.log(`User ${userData.displayName} already exists`);
return;
// return;
} else {
console.log(`User ${userData.displayName} does not exist, creating new user`);
}
const playerData = {
name: userData.displayName,
role: CONST.USER_ROLES.PLAYER,
password: "",
password: Alpha5PlayerManager.generateRandomPassword(),
color: "#" + Math.floor(Math.random() * 16777215).toString(16),
permissions: {},
flags: {
"alpha-5": {
[Alpha5Utils.getCurrentModuleId()]: {
discordId: userData.discordId,
tag: userData.tag,
alpha5Id: userData.id,
worldId: game.world.id,
worldId: game.world.title,
lastSync: new Date().toISOString(),
hasSeenWelcome: false
hasSeenWelcome: false,
imported: true
},
},
};
......@@ -71,7 +78,7 @@ export class Alpha5PlayerManager {
}
},
flags: {
"alpha-5": {
[Alpha5Utils.getCurrentModuleId()]: {
isTestCharacter: userData.discordId.startsWith("test_")
}
},
......@@ -111,7 +118,7 @@ export class Alpha5PlayerManager {
static async createTestUser() {
// Only GMs can create test users
if (!game.user.isGM) {
console.warn("Alpha-5 | Only GMs can create test users");
console.warn(Alpha5Utils.formatLogMessage("Only GMs can create test users"));
return;
}
......@@ -156,8 +163,18 @@ export class Alpha5PlayerManager {
ui.notifications.info(`Deleted ${testUsers.length} test users and their characters`);
} catch (error) {
console.error("Error deleting test users:", error);
console.error(Alpha5Utils.formatLogMessage("Error deleting test users:"), error);
ui.notifications.error("Failed to delete some test users");
}
}
static generateRandomPassword() {
const length = 16;
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
let password = "";
for (let i = 0; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
return password;
}
}
\ No newline at end of file
import { Alpha5 } from '../Alpha5.js';
import { Alpha5PlayerManager } from '../player/Alpha5PlayerManager.js';
import { Alpha5API } from '../api/Alpha5API.js';
import { Alpha5Utils } from '../api/Alpha5Utils.js';
export class Alpha5Settings {
static #syncIntervalId = null;
static registerSettings() {
game.settings.register("alpha-5", "apiKey", {
name: game.i18n.localize("alpha-5.settings.apiKey.name"),
hint: game.i18n.localize("alpha-5.settings.apiKey.hint"),
const moduleId = Alpha5Utils.getCurrentModuleId();
// Always use base module ID for localization keys
const localizationId = "alpha-5";
game.settings.register(moduleId, "apiKey", {
name: game.i18n.localize(`${localizationId}.settings.apiKey.name`),
hint: game.i18n.localize(`${localizationId}.settings.apiKey.hint`),
scope: "world",
config: true,
type: String,
......@@ -16,9 +21,9 @@ export class Alpha5Settings {
onChange: () => Alpha5.syncPlayers(),
});
game.settings.register("alpha-5", "autoSync", {
name: game.i18n.localize("alpha-5.settings.autoSync.name"),
hint: game.i18n.localize("alpha-5.settings.autoSync.hint"),
game.settings.register(moduleId, "autoSync", {
name: game.i18n.localize(`${localizationId}.settings.autoSync.name`),
hint: game.i18n.localize(`${localizationId}.settings.autoSync.hint`),
scope: "world",
config: true,
type: Boolean,
......@@ -26,9 +31,9 @@ export class Alpha5Settings {
onChange: () => this.updateSyncInterval(),
});
game.settings.register("alpha-5", "syncInterval", {
name: game.i18n.localize("alpha-5.settings.syncInterval.name"),
hint: game.i18n.localize("alpha-5.settings.syncInterval.hint"),
game.settings.register(moduleId, "syncInterval", {
name: game.i18n.localize(`${localizationId}.settings.syncInterval.name`),
hint: game.i18n.localize(`${localizationId}.settings.syncInterval.hint`),
scope: "world",
config: true,
type: Number,
......@@ -41,38 +46,13 @@ export class Alpha5Settings {
onChange: () => this.updateSyncInterval(),
});
game.settings.registerMenu("alpha-5", "apiUserManagement", {
name: "Alpha-5 User Management",
label: "Manage API Users",
hint: "View and manage users from the Alpha-5 API",
icon: "fas fa-users-cog",
type: ApiUserManagementForm,
restricted: true
});
}
static updateSyncInterval() {
if (this.#syncIntervalId) {
clearInterval(this.#syncIntervalId);
this.#syncIntervalId = null;
}
if (game.settings.get("alpha-5", "autoSync")) {
const interval = game.settings.get("alpha-5", "syncInterval") * 60000;
this.#syncIntervalId = setInterval(() => Alpha5.syncPlayers(), interval);
console.log(
`Alpha-5 | Sync interval updated to ${game.settings.get("alpha-5", "syncInterval")} minutes`
);
}
}
}
// Define the form class here when FormApplication is guaranteed to be available
class ApiUserManagementForm extends FormApplication {
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
id: "alpha5-api-user-management",
title: "Alpha-5 User Management",
template: "modules/alpha-5/templates/api-user-management.html",
title: `${Alpha5Utils.getModuleName()} User Management`,
template: `modules/${Alpha5Utils.getCurrentModuleId()}/templates/api-user-management.html`,
width: 600,
height: 400,
resizable: true
......@@ -104,7 +84,7 @@ class ApiUserManagementForm extends FormApplication {
const confirmed = await Dialog.confirm({
title: "Delete User",
content: `<p>Are you sure you want to delete user <strong>${userName}</strong> from the Alpha-5 service?</p>
content: `<p>Are you sure you want to delete user <strong>${userName}</strong> from the ${Alpha5Utils.getModuleName()} service?</p>
<p class="notes">This will remove them from the API but won't affect existing Foundry users.</p>`,
yes: async () => {
const success = await Alpha5API.deleteUser(userId);
......@@ -118,3 +98,30 @@ class ApiUserManagementForm extends FormApplication {
});
}
}
game.settings.registerMenu(moduleId, "apiUserManagement", {
name: `${Alpha5Utils.getModuleName()} User Management`,
label: "Manage API Users",
hint: `View and manage users from the ${Alpha5Utils.getModuleName()} API`,
icon: "fas fa-users-cog",
type: ApiUserManagementForm,
restricted: true
});
}
static updateSyncInterval() {
if (this.#syncIntervalId) {
clearInterval(this.#syncIntervalId);
this.#syncIntervalId = null;
}
const moduleId = Alpha5Utils.getCurrentModuleId();
if (game.settings.get(moduleId, "autoSync")) {
const interval = game.settings.get(moduleId, "syncInterval") * 60000;
this.#syncIntervalId = setInterval(() => Alpha5.syncPlayers(), interval);
console.log(
Alpha5Utils.formatLogMessage(`Sync interval updated to ${game.settings.get(moduleId, "syncInterval")} minutes`)
);
}
}
}
\ No newline at end of file
import { Alpha5Utils } from '../api/Alpha5Utils.js';
export class Alpha5Welcome {
static WELCOME_JOURNAL_NAME = "Welcome to the Game!";
......@@ -98,7 +100,7 @@ export class Alpha5Welcome {
const user = game.users.get(userId);
if (!user) return;
const hasSeenWelcome = user.getFlag("alpha-5", "hasSeenWelcome");
const hasSeenWelcome = user.getFlag(Alpha5Utils.getCurrentModuleId(), "hasSeenWelcome");
if (hasSeenWelcome) return;
const welcomeJournal = await this.createWelcomeJournal();
......@@ -118,7 +120,7 @@ export class Alpha5Welcome {
console.log(`Showing welcome journal to user: ${user.name}`);
// Only update the flag if we successfully showed the journal
await user.setFlag("alpha-5", "hasSeenWelcome", true);
await user.setFlag(Alpha5Utils.getCurrentModuleId(), "hasSeenWelcome", true);
} catch (error) {
console.warn(`Could not show welcome journal to ${user.name}:`, error);
// Don't set the flag if we couldn't show the journal
......
{
"name": "alpha-5-module",
"version": "1.6.3",
"version": "1.7.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "alpha-5-module",
"version": "1.6.3",
"version": "1.7.0",
"license": "MIT",
"dependencies": {
"archiver": "^7.0.1"
......
{
"name": "alpha-5-module",
"version": "1.6.3",
"version": "1.7.0",
"description": "Alpha-5 FoundryVTT Module",
"private": true,
"scripts": {
......
#!/bin/bash
# Script to install development version of the module
set -e
# Configuration
REMOTE_HOST="marvin@192.168.86.27"
MODULE_NAME="alpha-5-dev"
MODULE_BASE_NAME="Alpha-5"
MODULE_TITLE="${MODULE_BASE_NAME} (Development)"
MODULE_PATH="foundry12data/Data/modules/${MODULE_NAME}"
echo "Installing ${MODULE_TITLE} Module..."
# Create temporary directory for dev installation
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
# Copy module files to temp directory
cp -r foundry_vtt/* "$TEMP_DIR/"
# Use the development manifest
cp "$TEMP_DIR/module-dev.json" "$TEMP_DIR/module.json"
# Remove dev manifest from the copy
rm -f "$TEMP_DIR/module-dev.json"
# Remove existing development module on remote
echo "Cleaning up existing development module..."
ssh $REMOTE_HOST "rm -rf $MODULE_PATH"
ssh $REMOTE_HOST "mkdir -p $MODULE_PATH"
# Copy development module to Foundry
echo "Copying development module to Foundry..."
scp -r "$TEMP_DIR"/* "$REMOTE_HOST:$MODULE_PATH"
echo ""
echo "Restarting Foundry..."
ssh $REMOTE_HOST "source ~/.nvm/nvm.sh && pm2 restart 0"
echo ""
echo "Development module installed successfully!"
echo "Module ID: ${MODULE_NAME}"
echo "Module Title: ${MODULE_TITLE}"
\ No newline at end of file
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Read production module.json
const prodManifestPath = path.join(process.cwd(), 'foundry_vtt', 'module.json');
const devManifestPath = path.join(process.cwd(), 'foundry_vtt', 'module-dev.json');
try {
// Read both manifests
const prodManifest = JSON.parse(fs.readFileSync(prodManifestPath, 'utf8'));
const devManifest = JSON.parse(fs.readFileSync(devManifestPath, 'utf8'));
// Update dev version based on production version
const prodVersion = prodManifest.version;
devManifest.version = `${prodVersion}-dev`;
// Write updated dev manifest
fs.writeFileSync(devManifestPath, JSON.stringify(devManifest, null, 2) + '\n');
console.log(`✓ Synchronized dev version to: ${devManifest.version}`);
console.log(` Production: ${prodVersion}`);
console.log(` Development: ${devManifest.version}`);
} catch (error) {
console.error('Error synchronizing versions:', error.message);
process.exit(1);
}
\ No newline at end of file