Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/i18n/resources/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1561,16 +1561,26 @@ export const de: TranslationTree = {
filePath: "Dateipfad:",
color: "Farbe:",
refreshMinutes: "Aktualisierung (Min):",
authentication: "Authentifizierung:",
username: "Benutzername:",
password: "Passwort:",
},
typeOptions: {
remote: "Remote URL",
local: "Lokale Datei",
},
authOptions: {
none: "Keine (Öffentlich)",
basic: "HTTP Basic Auth",
},
authWarning: "Anmeldedaten werden unverschlüsselt in den Plugin-Daten gespeichert. Verwende wenn möglich anwendungsspezifische Passwörter.",
placeholders: {
calendarName: "Kalendername",
url: "ICS/iCal URL",
filePath: "Lokaler Dateipfad (z.B. Kalender.ics)",
localFile: "Kalender.ics",
username: "Benutzername",
password: "Passwort",
},
statusLabels: {
enabled: "Aktiviert",
Expand Down Expand Up @@ -2578,6 +2588,7 @@ export const de: TranslationTree = {
notices: {
calendarNotFound: "Kalender \"{name}\" nicht gefunden (404). Bitte prüfe, ob die ICS-URL korrekt ist und der Kalender öffentlich zugänglich ist.",
calendarAccessDenied: "Kalender \"{name}\" Zugriff verweigert (500). Dies könnte auf Microsoft Outlook Server-Beschränkungen zurückzuführen sein. Versuche, die ICS-URL aus deinen Kalendereinstellungen neu zu generieren.",
authenticationFailed: "Authentifizierung für Kalender \"{name}\" fehlgeschlagen. Bitte überprüfe deinen Benutzernamen und dein Passwort.",
fetchRemoteFailed: "Remote-Kalender \"{name}\" konnte nicht abgerufen werden: {error}",
readLocalFailed: "Lokaler Kalender \"{name}\" konnte nicht gelesen werden: {error}",
},
Expand Down
12 changes: 12 additions & 0 deletions src/i18n/resources/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1599,16 +1599,26 @@ export const en: TranslationTree = {
filePath: "File Path:",
color: "Color:",
refreshMinutes: "Refresh (min):",
authentication: "Authentication:",
username: "Username:",
password: "Password:",
},
typeOptions: {
remote: "Remote URL",
local: "Local File",
},
authOptions: {
none: "None (Public)",
basic: "HTTP Basic Auth",
},
authWarning: "Credentials are stored unencrypted in plugin data. Use app-specific passwords when available.",
placeholders: {
calendarName: "Calendar name",
url: "ICS/iCal URL",
filePath: "Local file path (e.g., Calendar.ics)",
localFile: "Calendar.ics",
username: "Username",
password: "Password",
},
statusLabels: {
enabled: "Enabled",
Expand Down Expand Up @@ -2638,6 +2648,8 @@ export const en: TranslationTree = {
'Calendar "{name}" not found (404). Please check the ICS URL is correct and the calendar is publicly accessible.',
calendarAccessDenied:
'Calendar "{name}" access denied (500). This may be due to Microsoft Outlook server restrictions. Try regenerating the ICS URL from your calendar settings.',
authenticationFailed:
'Authentication failed for calendar "{name}". Please check your username and password are correct.',
fetchRemoteFailed: 'Failed to fetch remote calendar "{name}": {error}',
readLocalFailed: 'Failed to read local calendar "{name}": {error}',
},
Expand Down
11 changes: 11 additions & 0 deletions src/i18n/resources/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1561,16 +1561,26 @@ export const es: TranslationTree = {
filePath: "Ruta de archivo:",
color: "Color:",
refreshMinutes: "Actualizar (min):",
authentication: "Autenticación:",
username: "Usuario:",
password: "Contraseña:",
},
typeOptions: {
remote: "URL remota",
local: "Archivo local",
},
authOptions: {
none: "Ninguna (Público)",
basic: "HTTP Basic Auth",
},
authWarning: "Las credenciales se almacenan sin cifrar en los datos del plugin. Usa contraseñas específicas de aplicación cuando estén disponibles.",
placeholders: {
calendarName: "Nombre del calendario",
url: "URL ICS/iCal",
filePath: "Ruta de archivo local (ej. Calendario.ics)",
localFile: "Calendario.ics",
username: "Usuario",
password: "Contraseña",
},
statusLabels: {
enabled: "Habilitado",
Expand Down Expand Up @@ -2578,6 +2588,7 @@ export const es: TranslationTree = {
notices: {
calendarNotFound: "Calendario \"{name}\" no encontrado (404). Por favor verifica que la URL ICS sea correcta y el calendario sea públicamente accesible.",
calendarAccessDenied: "Acceso al calendario \"{name}\" denegado (500). Esto puede deberse a restricciones del servidor de Microsoft Outlook. Intenta regenerar la URL ICS desde la configuración de tu calendario.",
authenticationFailed: "Autenticación fallida para el calendario \"{name}\". Por favor verifica que tu usuario y contraseña sean correctos.",
fetchRemoteFailed: "Error al obtener calendario remoto \"{name}\": {error}",
readLocalFailed: "Error al leer calendario local \"{name}\": {error}",
},
Expand Down
11 changes: 11 additions & 0 deletions src/i18n/resources/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1561,16 +1561,26 @@ export const fr: TranslationTree = {
filePath: "Chemin du fichier :",
color: "Couleur :",
refreshMinutes: "Actualisation (min) :",
authentication: "Authentification :",
username: "Nom d'utilisateur :",
password: "Mot de passe :",
},
typeOptions: {
remote: "URL distante",
local: "Fichier local",
},
authOptions: {
none: "Aucune (Public)",
basic: "HTTP Basic Auth",
},
authWarning: "Les identifiants sont stockés en clair dans les données du plugin. Utilisez des mots de passe spécifiques à l'application si disponibles.",
placeholders: {
calendarName: "Nom du calendrier",
url: "URL ICS/iCal",
filePath: "Chemin du fichier local (ex. Calendrier.ics)",
localFile: "Calendrier.ics",
username: "Nom d'utilisateur",
password: "Mot de passe",
},
statusLabels: {
enabled: "Activé",
Expand Down Expand Up @@ -2578,6 +2588,7 @@ export const fr: TranslationTree = {
notices: {
calendarNotFound: "Calendrier \"{name}\" introuvable (404). Veuillez vérifier que l'URL ICS est correcte et que le calendrier est accessible publiquement.",
calendarAccessDenied: "Accès refusé au calendrier \"{name}\" (500). Cela peut être dû aux restrictions du serveur Microsoft Outlook. Essayez de régénérer l'URL ICS depuis les paramètres de votre calendrier.",
authenticationFailed: "Échec de l'authentification pour le calendrier \"{name}\". Veuillez vérifier votre nom d'utilisateur et mot de passe.",
fetchRemoteFailed: "Échec de la récupération du calendrier distant \"{name}\" : {error}",
readLocalFailed: "Échec de la lecture du calendrier local \"{name}\" : {error}",
},
Expand Down
11 changes: 11 additions & 0 deletions src/i18n/resources/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1561,16 +1561,26 @@ export const ja: TranslationTree = {
filePath: "ファイルパス:",
color: "色:",
refreshMinutes: "更新(分):",
authentication: "認証:",
username: "ユーザー名:",
password: "パスワード:",
},
typeOptions: {
remote: "リモートURL",
local: "ローカルファイル",
},
authOptions: {
none: "なし(公開)",
basic: "HTTP Basic Auth",
},
authWarning: "認証情報はプラグインデータに暗号化されずに保存されます。可能な場合はアプリ固有のパスワードを使用してください。",
placeholders: {
calendarName: "カレンダー名",
url: "ICS/iCal URL",
filePath: "ローカルファイルパス(例:Calendar.ics)",
localFile: "Calendar.ics",
username: "ユーザー名",
password: "パスワード",
},
statusLabels: {
enabled: "有効",
Expand Down Expand Up @@ -2578,6 +2588,7 @@ export const ja: TranslationTree = {
notices: {
calendarNotFound: "カレンダー\"{name}\"が見つかりません(404)。ICS URLが正しく、カレンダーが公開アクセス可能であることを確認してください。",
calendarAccessDenied: "カレンダー\"{name}\"のアクセスが拒否されました(500)。これはMicrosoft Outlookサーバーの制限によるものかもしれません。カレンダー設定からICS URLを再生成してみてください。",
authenticationFailed: "カレンダー\"{name}\"の認証に失敗しました。ユーザー名とパスワードが正しいか確認してください。",
fetchRemoteFailed: "リモートカレンダー\"{name}\"の取得に失敗しました:{error}",
readLocalFailed: "ローカルカレンダー\"{name}\"の読み込みに失敗しました:{error}",
},
Expand Down
15 changes: 13 additions & 2 deletions src/i18n/resources/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1563,17 +1563,27 @@ export const pt: TranslationTree = {
url: "URL:",
filePath: "Caminho do Arquivo:",
color: "Cor:",
refreshMinutes: "Atualizar (min):"
refreshMinutes: "Atualizar (min):",
authentication: "Autenticação:",
username: "Usuário:",
password: "Senha:"
},
typeOptions: {
remote: "URL Remota",
local: "Arquivo Local"
},
authOptions: {
none: "Nenhuma (Público)",
basic: "HTTP Basic Auth"
},
authWarning: "As credenciais são armazenadas sem criptografia nos dados do plugin. Use senhas específicas do aplicativo quando disponíveis.",
placeholders: {
calendarName: "Nome do calendário",
url: "URL ICS/iCal",
filePath: "Caminho do arquivo local (ex: Calendario.ics)",
localFile: "Calendario.ics"
localFile: "Calendario.ics",
username: "Usuário",
password: "Senha"
},
statusLabels: {
enabled: "Ativado",
Expand Down Expand Up @@ -2586,6 +2596,7 @@ export const pt: TranslationTree = {
notices: {
calendarNotFound: 'Calendário "{name}" não encontrado (404). Por favor, verifique se a URL ICS está correta e se o calendário é acessível publicamente.',
calendarAccessDenied: 'Acesso ao calendário "{name}" negado (500). Isso pode ser devido a restrições do servidor Microsoft Outlook. Tente regenerar a URL ICS das configurações do seu calendário.',
authenticationFailed: 'Autenticação falhou para o calendário "{name}". Por favor, verifique se seu usuário e senha estão corretos.',
fetchRemoteFailed: 'Falha ao buscar calendário remoto "{name}": {error}',
readLocalFailed: 'Falha ao ler calendário local "{name}": {error}'
}
Expand Down
11 changes: 11 additions & 0 deletions src/i18n/resources/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1561,16 +1561,26 @@ export const ru: TranslationTree = {
filePath: "Путь к файлу:",
color: "Цвет:",
refreshMinutes: "Обновление (мин):",
authentication: "Аутентификация:",
username: "Имя пользователя:",
password: "Пароль:",
},
typeOptions: {
remote: "Удаленный URL",
local: "Локальный файл",
},
authOptions: {
none: "Нет (Публичный)",
basic: "HTTP Basic Auth",
},
authWarning: "Учетные данные хранятся в незашифрованном виде в данных плагина. Используйте пароли приложений, если доступны.",
placeholders: {
calendarName: "Имя календаря",
url: "URL ICS/iCal",
filePath: "Путь к локальному файлу (например, Календарь.ics)",
localFile: "Календарь.ics",
username: "Имя пользователя",
password: "Пароль",
},
statusLabels: {
enabled: "Включено",
Expand Down Expand Up @@ -2578,6 +2588,7 @@ export const ru: TranslationTree = {
notices: {
calendarNotFound: "Календарь \"{name}\" не найден (404). Пожалуйста, проверьте, что URL ICS правильный и календарь общедоступен.",
calendarAccessDenied: "Доступ к календарю \"{name}\" запрещен (500). Это может быть из-за ограничений сервера Microsoft Outlook. Попробуйте перегенерировать URL ICS из настроек календаря.",
authenticationFailed: "Ошибка аутентификации для календаря \"{name}\". Пожалуйста, проверьте правильность имени пользователя и пароля.",
fetchRemoteFailed: "Не удалось получить удаленный календарь \"{name}\": {error}",
readLocalFailed: "Не удалось прочитать локальный календарь \"{name}\": {error}",
},
Expand Down
11 changes: 11 additions & 0 deletions src/i18n/resources/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1561,16 +1561,26 @@ export const zh: TranslationTree = {
filePath: "文件路径:",
color: "颜色:",
refreshMinutes: "刷新(分钟):",
authentication: "认证:",
username: "用户名:",
password: "密码:",
},
typeOptions: {
remote: "远程URL",
local: "本地文件",
},
authOptions: {
none: "无(公开)",
basic: "HTTP Basic Auth",
},
authWarning: "凭据以明文存储在插件数据中。请尽可能使用应用程序专用密码。",
placeholders: {
calendarName: "日历名称",
url: "ICS/iCal URL",
filePath: "本地文件路径(例如,Calendar.ics)",
localFile: "Calendar.ics",
username: "用户名",
password: "密码",
},
statusLabels: {
enabled: "已启用",
Expand Down Expand Up @@ -2578,6 +2588,7 @@ export const zh: TranslationTree = {
notices: {
calendarNotFound: "找不到日历\"{name}\"(404)。请检查ICS URL是否正确且日历可公开访问。",
calendarAccessDenied: "日历\"{name}\"访问被拒绝(500)。这可能是由于Microsoft Outlook服务器限制。尝试从日历设置重新生成ICS URL。",
authenticationFailed: "日历\"{name}\"认证失败。请检查您的用户名和密码是否正确。",
fetchRemoteFailed: "获取远程日历\"{name}\"失败:{error}",
readLocalFailed: "读取本地日历\"{name}\"失败:{error}",
},
Expand Down
56 changes: 48 additions & 8 deletions src/services/ICSSubscriptionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,24 @@ export class ICSSubscriptionService extends EventEmitter {
throw new Error("Remote subscription missing URL");
}

// Build headers with optional Basic Auth
const headers: Record<string, string> = {
Accept: "text/calendar,*/*;q=0.1",
"Accept-Language": "en-US,en;q=0.9",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
};

// Add HTTP Basic Auth header for Baikal/Davis/CalDAV servers
if (subscription.authType === "basic" && subscription.username && subscription.password) {
const credentials = btoa(`${subscription.username}:${subscription.password}`);
headers["Authorization"] = `Basic ${credentials}`;
}

const response = await requestUrl({
url: subscription.url,
method: "GET",
headers: {
Accept: "text/calendar,*/*;q=0.1",
"Accept-Language": "en-US,en;q=0.9",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
},
headers,
});

icsData = response.text;
Expand Down Expand Up @@ -262,7 +271,13 @@ export class ICSSubscriptionService extends EventEmitter {

// Show user notification for errors with more helpful message
if (subscription.type === "remote") {
if (errorMessage.includes("404")) {
if (errorMessage.includes("401") || errorMessage.includes("Unauthorized")) {
new Notice(
this.translate("services.icsSubscription.notices.authenticationFailed", {
name: subscription.name,
})
);
} else if (errorMessage.includes("404")) {
new Notice(
this.translate("services.icsSubscription.notices.calendarNotFound", {
name: subscription.name,
Expand Down Expand Up @@ -298,6 +313,27 @@ export class ICSSubscriptionService extends EventEmitter {

private parseICS(icsData: string, subscriptionId: string): ICSEvent[] {
try {
// Basic validation - check if this looks like ICS data
const trimmedData = icsData.trim();
if (!trimmedData.startsWith("BEGIN:VCALENDAR")) {
// Log what we received for debugging
const preview = trimmedData.substring(0, 500);
console.error("ICS parse error: Response does not appear to be ICS data");
console.error("Response preview:", preview);

// Check for common CalDAV/WebDAV error responses
if (trimmedData.includes("<!DOCTYPE") || trimmedData.includes("<html")) {
throw new Error("Received HTML instead of ICS data. Check the calendar URL - for CalDAV servers like Baikal/Davis, you may need to append '?export' to the calendar URL.");
}
if (trimmedData.includes("<?xml") || trimmedData.includes("<multistatus")) {
throw new Error("Received WebDAV XML response. For CalDAV servers, use the direct ICS export URL (usually ends with '?export').");
}
if (trimmedData.includes("Unauthorized") || trimmedData.includes("401")) {
throw new Error("Authentication required or failed. Please check your username and password.");
}
throw new Error("Invalid ICS format - server did not return calendar data");
}

const jcalData = ICAL.parse(icsData);
const comp = new ICAL.Component(jcalData);

Expand Down Expand Up @@ -475,7 +511,11 @@ export class ICSSubscriptionService extends EventEmitter {
return events;
} catch (error) {
console.error("Failed to parse ICS data:", error);
throw new Error("Invalid ICS format");
// Re-throw with more context if it's already a detailed error
if (error instanceof Error && error.message !== "Invalid ICS format") {
throw error;
}
throw new Error("Invalid ICS format - could not parse calendar data");
}
}

Expand Down
Loading