@iflow-mcp/dearcloud09-logseq-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Logseq MCP Type Definitions
3
+ */
4
+
5
+ export interface Page {
6
+ path: string;
7
+ name: string;
8
+ content: string;
9
+ properties: Record<string, unknown>;
10
+ tags: string[];
11
+ links: string[];
12
+ backlinks: string[];
13
+ isJournal: boolean;
14
+ createdAt?: Date;
15
+ modifiedAt?: Date;
16
+ }
17
+
18
+ export interface PageMetadata {
19
+ path: string;
20
+ name: string;
21
+ tags: string[];
22
+ links: string[];
23
+ backlinks: string[];
24
+ isJournal: boolean;
25
+ }
26
+
27
+ export interface Block {
28
+ id: string;
29
+ content: string;
30
+ children: Block[];
31
+ properties: Record<string, unknown>;
32
+ }
33
+
34
+ export interface SearchResult {
35
+ page: PageMetadata;
36
+ matches: SearchMatch[];
37
+ }
38
+
39
+ export interface SearchMatch {
40
+ line: number;
41
+ content: string;
42
+ context: string;
43
+ }
44
+
45
+ export interface GraphNode {
46
+ id: string;
47
+ name: string;
48
+ type: 'page' | 'tag' | 'journal';
49
+ }
50
+
51
+ export interface GraphEdge {
52
+ source: string;
53
+ target: string;
54
+ type: 'link' | 'tag' | 'backlink';
55
+ }
56
+
57
+ export interface Graph {
58
+ nodes: GraphNode[];
59
+ edges: GraphEdge[];
60
+ }
61
+
62
+ export interface GraphConfig {
63
+ path: string;
64
+ journalsPath: string;
65
+ pagesPath: string;
66
+ }
@@ -0,0 +1,249 @@
1
+ import axios from 'axios';
2
+ import * as cheerio from 'cheerio';
3
+
4
+ interface WeatherData {
5
+ description: string;
6
+ temperature: number;
7
+ minTemperature?: number;
8
+ maxTemperature?: number;
9
+ feelsLike?: number;
10
+ humidity?: number;
11
+ windSpeed?: number;
12
+ pm10?: string;
13
+ pm25?: string;
14
+ uvIndex?: string;
15
+ sunset?: string;
16
+ }
17
+
18
+ export class WeatherScraper {
19
+ private location: string;
20
+
21
+ constructor(location?: string) {
22
+ this.location = location || process.env.WEATHER_LOCATION || '서울';
23
+ }
24
+
25
+ async getWeather(): Promise<WeatherData | null> {
26
+ return await this.getWeatherFromNaver();
27
+ }
28
+
29
+ // 하위 호환성 유지
30
+ async getWeatherForDongcheon(): Promise<WeatherData | null> {
31
+ return await this.getWeather();
32
+ }
33
+
34
+ private async getWeatherFromNaver(): Promise<WeatherData | null> {
35
+ try {
36
+ const url = `https://search.naver.com/search.naver?query=${encodeURIComponent(this.location)}+날씨`;
37
+
38
+ const response = await axios.get(url, {
39
+ headers: {
40
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
41
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
42
+ 'Accept-Language': 'ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3',
43
+ }
44
+ });
45
+
46
+ const $ = cheerio.load(response.data);
47
+
48
+ console.log('네이버 검색 결과에서 날씨 정보 추출 중...');
49
+
50
+ // 현재 온도
51
+ let temperature = 0;
52
+ const temperatureSelectors = [
53
+ '.temperature_text',
54
+ '.main_temp',
55
+ '.current strong',
56
+ '.today_area .temp'
57
+ ];
58
+
59
+ for (const selector of temperatureSelectors) {
60
+ const tempText = $(selector).text();
61
+ const tempMatch = tempText.match(/-?\d+(?:\.\d+)?/);
62
+ if (tempMatch) {
63
+ temperature = Math.round(parseFloat(tempMatch[0]));
64
+ break;
65
+ }
66
+ }
67
+
68
+ // 날씨 상태
69
+ let description = '맑음';
70
+ const descSelectors = [
71
+ '.weather_main',
72
+ '.cast_txt',
73
+ '.summary_txt',
74
+ '.today_area .main .cast_txt',
75
+ '.current .weather',
76
+ '.weather_description'
77
+ ];
78
+
79
+ for (const selector of descSelectors) {
80
+ const descText = $(selector).first().text().trim();
81
+ if (descText && !descText.includes('°') && !descText.includes('어제') && descText.length < 10) {
82
+ description = descText;
83
+ break;
84
+ }
85
+ }
86
+
87
+ if (description.length > 10) {
88
+ const firstWord = description.split(/\s+/)[0];
89
+ if (firstWord && firstWord.length < 10) {
90
+ description = firstWord;
91
+ }
92
+ }
93
+
94
+ // 최저/최고 온도
95
+ let minTemperature: number | undefined;
96
+ let maxTemperature: number | undefined;
97
+
98
+ const wholeText = $('body').text();
99
+
100
+ const tempRangeMatch1 = wholeText.match(/(\d+)°\s*(\d+)°/);
101
+ if (tempRangeMatch1) {
102
+ minTemperature = parseInt(tempRangeMatch1[1]);
103
+ maxTemperature = parseInt(tempRangeMatch1[2]);
104
+ }
105
+
106
+ if (!minTemperature || !maxTemperature) {
107
+ const koreanTempMatch = wholeText.match(/최저[^\d]*(\d+)[^\d]*최고[^\d]*(\d+)/);
108
+ if (koreanTempMatch) {
109
+ minTemperature = parseInt(koreanTempMatch[1]);
110
+ maxTemperature = parseInt(koreanTempMatch[2]);
111
+ }
112
+ }
113
+
114
+ // 최저가 최고보다 크면 swap (네이버에서 순서가 다를 수 있음)
115
+ if (minTemperature !== undefined && maxTemperature !== undefined && minTemperature > maxTemperature) {
116
+ [minTemperature, maxTemperature] = [maxTemperature, minTemperature];
117
+ }
118
+
119
+ // 체감온도
120
+ let feelsLike: number | undefined;
121
+ const infoText = $('.today_area .info_list').text() || $('.detail_box').text() || wholeText;
122
+ if (infoText.includes('체감')) {
123
+ const feelsMatch = infoText.match(/체감[^\d]*(\d+(?:\.\d+)?)/);
124
+ if (feelsMatch) {
125
+ feelsLike = Math.round(parseFloat(feelsMatch[1]));
126
+ }
127
+ }
128
+
129
+ // 습도
130
+ let humidity: number | undefined;
131
+ const humidityMatch = infoText.match(/습도[^\d]*(\d+)%/);
132
+ if (humidityMatch) {
133
+ humidity = parseInt(humidityMatch[1]);
134
+ }
135
+
136
+ // 바람
137
+ let windSpeed: number | undefined;
138
+ const windMatch = infoText.match(/(\d+(?:\.\d+)?)m\/s/);
139
+ if (windMatch) {
140
+ windSpeed = parseFloat(windMatch[1]);
141
+ }
142
+
143
+ // 미세먼지
144
+ let pm10: string | undefined;
145
+ const dustMatch = wholeText.match(/미세먼지[^\w]*(좋음|보통|나쁨|매우나쁨)/);
146
+ if (dustMatch) pm10 = dustMatch[1];
147
+
148
+ // 초미세먼지
149
+ let pm25: string | undefined;
150
+ const fineDustMatch = wholeText.match(/초미세먼지[^\w]*(좋음|보통|나쁨|매우나쁨)/);
151
+ if (fineDustMatch) pm25 = fineDustMatch[1];
152
+
153
+ // 자외선
154
+ let uvIndex: string | undefined;
155
+ const uvMatch = wholeText.match(/자외선[^\w]*(좋음|보통|나쁨|매우나쁨)/);
156
+ if (uvMatch) uvIndex = uvMatch[1];
157
+
158
+ // 일몰 시간
159
+ let sunset: string | undefined;
160
+ const sunsetMatch = wholeText.match(/일몰[^\d]*(\d{1,2}:\d{2})/);
161
+ if (sunsetMatch) sunset = sunsetMatch[1];
162
+
163
+ console.log('수집된 날씨 정보:', {
164
+ temperature,
165
+ description,
166
+ minTemperature,
167
+ maxTemperature,
168
+ feelsLike,
169
+ humidity,
170
+ windSpeed,
171
+ pm10,
172
+ pm25,
173
+ uvIndex,
174
+ sunset
175
+ });
176
+
177
+ // 온도가 0이거나 description만 있어도 반환 (0도도 유효한 온도)
178
+ // 최소한 하나의 정보라도 있으면 반환
179
+ const hasAnyData = temperature !== 0 || minTemperature || humidity || pm10;
180
+
181
+ if (description || hasAnyData) {
182
+ return {
183
+ description: description.trim() || '정보 없음',
184
+ temperature: temperature || minTemperature || 0,
185
+ minTemperature,
186
+ maxTemperature,
187
+ feelsLike,
188
+ humidity,
189
+ windSpeed,
190
+ pm10,
191
+ pm25,
192
+ uvIndex,
193
+ sunset
194
+ };
195
+ }
196
+
197
+ return null;
198
+ } catch (error) {
199
+ console.error('네이버 날씨 스크래핑 실패:', error);
200
+ return null;
201
+ }
202
+ }
203
+
204
+ formatWeatherForLogseq(weather: WeatherData): string[] {
205
+ const lines: string[] = [];
206
+
207
+ // 날씨 상태
208
+ lines.push(weather.description);
209
+
210
+ // 온도 정보
211
+ if (weather.minTemperature !== undefined && weather.maxTemperature !== undefined) {
212
+ lines.push(`최저 기온 ${weather.minTemperature}도, 최고 기온 ${weather.maxTemperature}도`);
213
+ } else {
214
+ lines.push(`현재 기온 ${weather.temperature}도`);
215
+ }
216
+
217
+ // 체감온도
218
+ if (weather.feelsLike !== undefined) {
219
+ lines.push(`체감온도 ${weather.feelsLike}도`);
220
+ }
221
+
222
+ // 습도
223
+ if (weather.humidity !== undefined) {
224
+ lines.push(`습도 ${weather.humidity}%`);
225
+ }
226
+
227
+ // 바람
228
+ if (weather.windSpeed !== undefined) {
229
+ lines.push(`바람 ${weather.windSpeed}m/s`);
230
+ }
231
+
232
+ // 미세먼지
233
+ if (weather.pm10) {
234
+ lines.push(`미세먼지 ${weather.pm10}`);
235
+ }
236
+
237
+ // 초미세먼지
238
+ if (weather.pm25) {
239
+ lines.push(`초미세먼지 ${weather.pm25}`);
240
+ }
241
+
242
+ // 자외선
243
+ if (weather.uvIndex) {
244
+ lines.push(`자외선 ${weather.uvIndex}`);
245
+ }
246
+
247
+ return lines;
248
+ }
249
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }