mirror of
https://github.com/aleksejs1/obsidian-contact-sync-plugin.git
synced 2026-07-22 12:40:24 +00:00
255 lines
7.1 KiB
TypeScript
255 lines
7.1 KiB
TypeScript
import type { ContactSyncSettings } from './types/Settings';
|
|
import { ContactSyncSettingTab } from './plugin/settings';
|
|
import { DEFAULT_SETTINGS } from './config';
|
|
import { Plugin, Notice } from 'obsidian';
|
|
import { AuthManager } from './auth/AuthManager';
|
|
import { GoogleContactsService } from './services/GoogleContactsService';
|
|
import { ContactNoteWriter } from './services/ContactNoteWriter';
|
|
import { ContactNoteConfig } from './types/ContactNoteConfig';
|
|
import { ContactAuditService } from './services/ContactAuditService';
|
|
import { t } from './i18n/translator';
|
|
import type { GoogleContact } from './types/Contact';
|
|
|
|
/**
|
|
* Obsidian plugin for synchronizing contacts from Google contacts into markdown notes.
|
|
*/
|
|
export default class GoogleContactsSyncPlugin extends Plugin {
|
|
/** Plugin settings loaded from user config */
|
|
settings: ContactSyncSettings = DEFAULT_SETTINGS;
|
|
|
|
/** Manages OAuth token exchange and refresh */
|
|
auth: AuthManager | null = null;
|
|
|
|
/**
|
|
* Service layer handling communication with the Google contacts API.
|
|
* Used to fetch contacts and groups, separate from Obsidian-specific logic.
|
|
*/
|
|
googleService: GoogleContactsService | null = null;
|
|
|
|
/** Handles writing contact notes to the vault */
|
|
noteWriter: ContactNoteWriter | null = null;
|
|
auditService: ContactAuditService | null = null;
|
|
|
|
/** ID of the interval used for periodic sync */
|
|
private syncIntervalId: number | null = null;
|
|
|
|
/**
|
|
* Called when the plugin is loaded by Obsidian.
|
|
*/
|
|
async onload() {
|
|
this.addCommand({
|
|
id: 'sync',
|
|
name: 'Sync',
|
|
callback: () => this.syncContacts(),
|
|
});
|
|
|
|
this.addCommand({
|
|
id: 'audit-contacts',
|
|
name: 'Audit Contacts',
|
|
callback: async () => {
|
|
const token = await this.auth?.ensureValidToken();
|
|
if (!token) {
|
|
new Notice(
|
|
t('Failed to obtain access token. Please re-authenticate.')
|
|
);
|
|
return;
|
|
}
|
|
await this.updateAuthSettings();
|
|
await this.auditService?.auditContacts(token);
|
|
},
|
|
});
|
|
|
|
await this.loadSettings();
|
|
this.auth = new AuthManager(this.settings);
|
|
this.addSettingTab(new ContactSyncSettingTab(this.app, this));
|
|
this.googleService = new GoogleContactsService();
|
|
this.noteWriter = new ContactNoteWriter(
|
|
this.app.vault,
|
|
this.app.metadataCache,
|
|
this.app.fileManager
|
|
);
|
|
this.auditService = new ContactAuditService(
|
|
this.app,
|
|
this.googleService,
|
|
this.settings
|
|
);
|
|
|
|
if (this.settings.syncOnStartup || this.shouldSyncNow()) {
|
|
void this.syncContacts();
|
|
}
|
|
|
|
this.setupAutoSync();
|
|
}
|
|
|
|
onunload() {
|
|
if (this.syncIntervalId) {
|
|
clearInterval(this.syncIntervalId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets up automatic periodic syncing using setInterval based on plugin settings.
|
|
*/
|
|
setupAutoSync() {
|
|
if (this.syncIntervalId) {
|
|
clearInterval(this.syncIntervalId);
|
|
}
|
|
|
|
const interval = this.settings.syncIntervalMinutes;
|
|
|
|
if (interval > 0) {
|
|
this.registerInterval(
|
|
window.setInterval(
|
|
() => {
|
|
if (this.shouldSyncNow()) {
|
|
void this.syncContacts();
|
|
}
|
|
},
|
|
interval * 60 * 1000
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines whether syncing should be triggered based on the last sync time and interval.
|
|
* @returns True if sync should be triggered now, false otherwise.
|
|
*/
|
|
shouldSyncNow(): boolean {
|
|
const { lastSyncTime, syncIntervalMinutes } = this.settings;
|
|
|
|
if (syncIntervalMinutes === 0) {
|
|
return false;
|
|
}
|
|
if (!lastSyncTime) {
|
|
return true;
|
|
}
|
|
|
|
const last = new Date(lastSyncTime).getTime();
|
|
const now = Date.now();
|
|
const diffMinutes = (now - last) / 1000 / 60;
|
|
|
|
return diffMinutes >= syncIntervalMinutes;
|
|
}
|
|
|
|
/**
|
|
* Performs the contact synchronization process: fetching, processing, and saving contact notes.
|
|
*/
|
|
async syncContacts() {
|
|
void this.updateLastSyncTime();
|
|
|
|
const token = await this.getToken();
|
|
if (!token) {
|
|
return;
|
|
}
|
|
|
|
const labelMap = await this.getLabelMap(token);
|
|
if (!labelMap) {
|
|
return;
|
|
}
|
|
|
|
const contacts = await this.getContacts(token);
|
|
if (!contacts) {
|
|
return;
|
|
}
|
|
|
|
const config = this.getNoteConfig();
|
|
await this.noteWriter?.writeNotesForContacts(config, labelMap, contacts);
|
|
|
|
new Notice(t('Google contacts synced!'));
|
|
}
|
|
|
|
private async getToken(): Promise<string | null> {
|
|
const token = await this.auth?.ensureValidToken();
|
|
if (!token) {
|
|
new Notice(t('Failed to obtain access token. Please re-authenticate.'));
|
|
return null;
|
|
}
|
|
void this.updateAuthSettings();
|
|
return token;
|
|
}
|
|
|
|
private async getLabelMap(
|
|
token: string
|
|
): Promise<Record<string, string> | null> {
|
|
try {
|
|
return (await this.googleService?.fetchGoogleGroups(token)) ?? {};
|
|
} catch (error) {
|
|
console.error(
|
|
'Failed to fetch Google groups',
|
|
JSON.stringify(error, null, 2)
|
|
);
|
|
new Notice(
|
|
t('Failed to fetch Google groups. Check console for details.')
|
|
);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private async getContacts(token: string): Promise<GoogleContact[] | null> {
|
|
try {
|
|
return (await this.googleService?.fetchGoogleContacts(token)) ?? [];
|
|
} catch (error) {
|
|
console.error(
|
|
'Failed to fetch Google contacts',
|
|
JSON.stringify(error, null, 2)
|
|
);
|
|
new Notice(
|
|
t('Failed to fetch Google contacts. Check console for details.')
|
|
);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private getNoteConfig(): ContactNoteConfig {
|
|
return {
|
|
folderPath: this.settings.contactsFolder,
|
|
prefix: this.settings.fileNamePrefix,
|
|
propertyPrefix: this.settings.propertyNamePrefix,
|
|
syncLabel: this.settings.syncLabel,
|
|
noteBody: this.settings.noteTemplate || '# Notes\n',
|
|
organizationAsLink: this.settings.organizationAsLink,
|
|
relationsAsLink: this.settings.relationsAsLink,
|
|
trackSyncTime: this.settings.trackSyncTime,
|
|
renameFiles: this.settings.renameFiles,
|
|
namingStrategy: this.settings.namingStrategy,
|
|
lastFirst: this.settings.lastFirst,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Updates the last synchronization timestamp and saves the updated plugin settings.
|
|
* This should be called after a successful sync to persist the last sync time.
|
|
*/
|
|
async updateLastSyncTime(): Promise<void> {
|
|
this.settings.lastSyncTime = new Date().toISOString();
|
|
await this.saveSettings();
|
|
}
|
|
|
|
/**
|
|
* Updates the plugin settings with the latest authentication tokens from the AuthManager
|
|
* and persists them to the plugin storage.
|
|
*/
|
|
async updateAuthSettings(): Promise<void> {
|
|
Object.assign(this.settings, this.auth?.getSettingsUpdate());
|
|
await this.saveSettings();
|
|
}
|
|
|
|
/**
|
|
* Loads plugin settings from disk, applying defaults as needed.
|
|
*/
|
|
async loadSettings() {
|
|
this.settings = Object.assign(
|
|
{},
|
|
DEFAULT_SETTINGS,
|
|
(await this.loadData()) as Partial<ContactSyncSettings>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Saves current plugin settings to disk.
|
|
*/
|
|
async saveSettings() {
|
|
await this.saveData(this.settings);
|
|
}
|
|
}
|