Obsidian Templater + Google calendar + Tasks 플러그인 예제
안녕하세요. 오늘은 옵시디언의 템플레이터(Templater)를 이용해 구글 캘린더의 일정을 긁어와서 뿌려주는 기능을 소개드립니다.
사전준비
플러그인
Templater 플러그인
- 데일리노트에 스크립트가 적용된 코드를 삽입하여 생성시 자동 실행하기에 유용한 플러그인 입니다.
- 이 포스트에서 사용합니다.
title: "GitHub - SilentVoid13/Templater: A template plugin for obsidian"
image: "https://opengraph.githubassets.com/f5160f6b65effee444e51a37f0bc2f0f8e9443929d0ab0be42af0801fb809ad6/SilentVoid13/Templater"
description: "A template plugin for obsidian. Contribute to SilentVoid13/Templater development by creating an account on GitHub."
url: "https://github.com/SilentVoid13/Templater"
Google Calender 플러그인
- API를 통해 연동한 정보를 가져올 수 있는 플러그인 입니다.
- 역시 이 포스트를 따라하실 때 필요합니다.
- 아래 페이지를 통해서 연동해주세요!
title: "Setup"
image: "https://yukigasai.github.io/obsidian-google-calendar/static/og-image.png"
description: "The installation of this plugin is a little cumbersome. But after the initial setup you should never have to touch it again. The creation of a public OAUTH client to skip this setup is WIP."
url: "https://yukigasai.github.io/obsidian-google-calendar/Setup"
Tasks 플러그인 이 예제를 통해 출력된 일정을 관리하고자 할 때 필요합니다.
title: "GitHub - obsidian-tasks-group/obsidian-tasks: Task management for the Obsidian knowledge base."
image: "https://opengraph.githubassets.com/613f4623358f1f53546e72aa4f7cb36ef061ff08305f03022ffd0a3833c08065/obsidian-tasks-group/obsidian-tasks"
description: "Task management for the Obsidian knowledge base. Contribute to obsidian-tasks-group/obsidian-tasks development by creating an account on GitHub."
url: "https://github.com/obsidian-tasks-group/obsidian-tasks"
기타 저는 일반적으로 데일리 노트를 생성할때, 미리 설정된 포맷에 링크를 걸어두어 잘 사용하지 않지만 좌측 리본의 ‘Open Today’와 같은 버튼 바로가기로 생성하고 싶으시다면 별도 플러그인을 설치해두시는 것이 좋습니다. 저는 코어 플러그인의 데일리노트는 사용하지 않고, Pediodic note 플러그인을 사용하고 있습니다.
구글 캘린더 설정
저는 캘린더를 여러개 사용합니다. 특히 연동되었거나 반복된 일정의 경우 별도 캘린더를 추가하여 일정을 가져올때 무시하거나, 태그를 추가할 때 사용합니다.
‘다른 캘린더’ 우측의 + 버튼
을 통해 새로운 캘린더를 생성할 수 있습니다.
연동된 계정 기준 안드로이드 기본 캘린더 앱, 삼성 갤럭시 기본 일정 앱의 경우 추가한 캘린더로 일정등록 및 수정이 가능했습니다.
코드 설명
구글 캘린더
<%*
const {getEvents} = this.app.plugins.plugins["google-calendar"].api;
const theEvents = await getEvents({
startDate: window.moment(tp.file.title, 'YYYY-MM-DD'),
endDate: window.moment(tp.file.title, 'YYYY-MM-DD')
});
tR = theEvents.reduce((text, event) => {
return text;
}, "");
%>
구글 캘린더 플러그인을 통해 현재 파일의 제목이 시작날짜~종료날짜 인 항목에 대해서 캘린더의 일정을 가져오는 API 입니다.
- 저는 날짜 포맷을 YYYY-MM-DD로 사용하고 있어서 그대로 넣었습니다.
- 코드에서는 tp.file.title, 즉 생성된 노트의 제목을 기준으로 캘린더를 호출합니다.
예외처리
tR = theEvents.reduce((text, event) => {
if( event.organizer){
//캘린더명이 Todoist인것과 Routine인 것은 아래 내용을 수행하지 않고 반환합니다.
if( event.organizer.displayName === "Todoist" || event.organizer.displayName === "Routine"){
return text;
}
}
저는 Todoist, Routine 캘린더에 등록된 일정은 무시하고 싶었기 때문에 이렇게 작성했습니다.
event.organizer.displayName
은 앞서 만든 캘린더의 이름입니다.
태그달기
if(event.organizer.displayName === "Apply"){
specialTag = '#apply';
} else if(event.organizer.displayName === "coupon"){
specialTag = '#coupon';
}
...
text += `- [ ] #event ${specialTag} ${timeSummary}**${urlTitle}** ➕ ${tp.file.title} 🛫 ${tp.file.title} 📅 ${endDate}`;
동일하게, event.organizer.displayName
의 구별을 통해 태그를 추가해줄 수 있습니다.
출력포맷
시간 출력
function makeTimeString(event){
const startDateTime = new Date(event.start.dateTime);
const endDateTime = new Date(event.end.dateTime);
// 시작시간 및 종료시간이 없을 경우 하루종일이라고 판단합니다.
if( isNaN(startDateTime) && isNaN(endDateTime) ){
return {formattedStartTime:"", formattedDetail:"하루종일"};
}
console.log(startDateTime+","+endDateTime);
// 종료시간만 없을 경우 시작시간만 표기합니다.
const formattedStartTime = startDateTime.getHours().toString().padStart(2, '0') + ':' + startDateTime.getMinutes().toString().padStart(2, '0');
if( isNaN(endDateTime) ) {
return {formattedStartTime, formattedDetail:"하루종일"};
}
const formattedEndTime = endDateTime.getHours().toString().padStart(2, '0') + ':' + endDateTime.getMinutes().toString().padStart(2, '0');
// 시작 종료일이 다른 경우 표기합니다.,
const isSameDate = startDateTime.toDateString() === endDateTime.toDateString();
let endDateDisplay = formattedEndTime;
if (!isSameDate) {
endDateDisplay = (endDateTime.getMonth() + 1).toString().padStart(2, '0') + '-' +
endDateTime.getDate().toString().padStart(2, '0') + '(' +
endDateTime.toLocaleString('en-US', { weekday: 'short' }) + ') ' +
formattedEndTime;
}
const duration = new Date(endDateTime - startDateTime);
const formattedDuration = duration.getUTCHours().toString().padStart(2, '0') + ':' + duration.getUTCMinutes().toString().padStart(2, '0');
const formattedDetail = formattedStartTime +"~"+ (isSameDate ? `${formattedEndTime} (${formattedDuration})` : endDateDisplay);
return {formattedStartTime,formattedDetail};
}
시간은 시작시간, 시작~종료시간을 출력할 수 있습니다.
출력 포맷팅
const timeString = makeTimeString(event);
//제목의 경우 요약에 특수문자가 있을 경우 이스케이프 처리를 하고, 구글 캘린더 url을 연결합니다.
const urlTitle = "[" + escapeMarkdown(summary) + "](" + event.htmlLink + ")";
//시간정보의 경우 시작시간을 표기하며 대괄호에 포함합니다.
const timeSummary = timeString.formattedStartTime ? "[" + timeString.formattedStartTime + "] " : "";
//사전 정의된 태그가 있을 경우 추가하여 표기합니다.
if (text) text += "\r\n";
if (specialTag) {
text += `- [ ] #event ${specialTag} ${timeSummary}**${urlTitle}** ➕ ${tp.file.title} 🛫 ${tp.file.title} 📅 ${endDate}`;
} else {
text += `- [ ] #event ${timeSummary}**${urlTitle}** ➕ ${tp.file.title} 📅 ${tp.file.title}`;
}
시간정보, 태그정보를 추가하여 결과를 작성합니다.,
- [ ] #event ${specialTag} ${timeSummary}**${urlTitle}** ➕ ${tp.file.title} 🛫 ${tp.file.title} 📅 ${endDate}
;`
- tasks 정보에 생성일을 현재날짜(파일제목), 예정일을 현재 날짜(파일 제목), 종료일을 기입하였습니다.
전체코드
## 오늘의 일정
<%*
const {getEvents} = this.app.plugins.plugins["google-calendar"].api;
const theEvents = await getEvents({
startDate: window.moment(tp.file.title, 'YYYY-MM-DD'),
endDate: window.moment(tp.file.title, 'YYYY-MM-DD')
});
// Function to escape markdown special characters
function escapeMarkdown(text) {
return text.replace(/\\/g, '\\\\') // Escape backslash
.replace(/^\*/g, '\-') // Escape asterisk
.replace(/\*/g, '') // Escape asterisk
.replace(/_/g, '\\_') // Escape underscore
.replace(/{/g, '\\{') // Escape curly braces
.replace(/}/g, '\\}')
.replace(/\[/g, '\\[') // Escape square brackets
.replace(/\]/g, '\\]')
.replace(/\(/g, '\\(') // Escape parentheses
.replace(/\)/g, '\\)')
.replace(/#/g, '\\#') // Escape hash
.replace(/\+/g, '\\+') // Escape plus
.replace(/-/g, '\\-') // Escape minus (hyphen)
.replace(/\./g, '\\.') // Escape dot
.replace(/!/g, '\\!'); // Escape exclamation mark
}
function escapeDetails(text){
var txt= text
.replace(//g, '>')
.replace(/\n\s?\n/g, '\r\n')
.replace(/\r\s*\n/g, '\r\n')
.replace(/\r\n/g, '\r\n')
.replace(/\s+\n/g, '\r\n')
.replace(/\r+\n/g, '\r\n')
.replace(/\r/g, '\r\n')
.replace(/\n+/g, '\r\n')
//.replace(/&/g, '&')
//.replace(/'/g, ''');
.replace(/\r+\n/g, '
')
;
//console.log(txt);
return txt;
}
// Function to convert URLs into markdown links
function linkify(text) {
const urlRegex = /\bhttps?:\/\/[^\s$.?#<].[^\s<]*\b/ig;
return text.replace(urlRegex, function(url) {
url = url.replace("\.",".");
// The matched URL is returned as a markdown link
console.log(url)
return '[' + url + '](' + url + ')';
});
}
function makeTimeString(event){
const startDateTime = new Date(event.start.dateTime);
const endDateTime = new Date(event.end.dateTime);
if( isNaN(startDateTime) && isNaN(endDateTime) ){
return {formattedStartTime:"", formattedDetail:"하루종일"};
}
console.log(startDateTime+","+endDateTime);
// Format the Date objects to extract hours and minutes
const formattedStartTime = startDateTime.getHours().toString().padStart(2, '0') + ':' + startDateTime.getMinutes().toString().padStart(2, '0');
if( isNaN(endDateTime) ) {
return {formattedStartTime, formattedDetail:"하루종일"};
}
const formattedEndTime = endDateTime.getHours().toString().padStart(2, '0') + ':' + endDateTime.getMinutes().toString().padStart(2, '0');
// Check if the start and end dates are the same
const isSameDate = startDateTime.toDateString() === endDateTime.toDateString();
// Format the end date and time
let endDateDisplay = formattedEndTime;
if (!isSameDate) {
endDateDisplay = (endDateTime.getMonth() + 1).toString().padStart(2, '0') + '-' +
endDateTime.getDate().toString().padStart(2, '0') + '(' +
endDateTime.toLocaleString('en-US', { weekday: 'short' }) + ') ' +
formattedEndTime;
}
// Calculate duration
const duration = new Date(endDateTime - startDateTime);
const formattedDuration = duration.getUTCHours().toString().padStart(2, '0') + ':' + duration.getUTCMinutes().toString().padStart(2, '0');
const formattedDetail = formattedStartTime +"~"+ (isSameDate ? `${formattedEndTime} (${formattedDuration})` : endDateDisplay);
return {formattedStartTime,formattedDetail};
}
function processSummary(summary) {
// Extract the day index and total day count from the summary
const dayPattern = /\((\d+)\/(\d+)\)$/;
const match = summary.match(dayPattern);
if (match) {
const [_, dayIndex, totalDays] = match;
if (dayIndex === '1') {
return {
summary: summary.replace(dayPattern, ''),
isDisplay: true,
totalDays
};
} else {
return { isDisplay: false };
}
}
return { summary, isDisplay: true };
}
tR = theEvents.reduce((text, event) => {
if( event.organizer){
if( event.organizer.displayName === "Todoist" || event.organizer.displayName === "Routine"){
return text;
}
}
let summary = event.summary ? event.summary : '';
let specialTag = '';
let endDate = "";
if (event.eventType === "multiDay" && event.organizer) {
if(event.organizer.displayName === "Apply"){
specialTag = '#apply';
} else if(event.organizer.displayName === "coupon"){
specialTag = '#coupon';
}
if (specialTag) {
const processedSummary = processSummary(event.summary);
if (!processedSummary.isDisplay) return text;
summary = processedSummary.summary;
let startDate = window.moment(tp.file.title, 'YYYY-MM-DD').format('YYYY-MM-DD');
endDate = processedSummary.totalDays ? window.moment(startDate).add(processedSummary.totalDays - 1, 'days').format('YYYY-MM-DD') : startDate;
}
}
const timeString = makeTimeString(event);
const urlTitle = "[" + escapeMarkdown(summary) + "](" + event.htmlLink + ")";
const timeSummary = timeString.formattedStartTime ? "[" + timeString.formattedStartTime + "] " : "";
if (text) text += "\r\n";
if (specialTag) {
text += `- [ ] #event ${specialTag} ${timeSummary}**${urlTitle}** ➕ ${tp.file.title} 🛫 ${tp.file.title} 📅 ${endDate}`;
} else {
text += `- [ ] #event ${timeSummary}**${urlTitle}** ➕ ${tp.file.title} 📅 ${tp.file.title}`;
}
return text;
}, "");
%>
출력 예시
이미지
![](https://i.imgur.com/0rBk7MF.png)템플릿 적용 결과 해당 폴더에 새로운 문서를 만들때 이름을 날짜로 지정하면, 위 사진과 같이 출력이 됩니다.