bloggerize 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/htmls.js ADDED
@@ -0,0 +1,110 @@
1
+ ///////////////////////////////////////////////////////////
2
+
3
+ globalThis.HTML_STYLE = '<style>img{max-width:100%;height:auto;}a{text-decoration:none;}a:link{color:#4287f5;}a:hover{color:red;}</style>';
4
+
5
+ globalThis.HTML_FORM = `
6
+ <!DOCTYPE html>
7
+ <meta charset="utf-8"/>
8
+ <meta name="viewport" content="width=device-width,initial-scale=1.0"/>
9
+ <html lang="en">
10
+ <head>{{head}}</head>
11
+ <body>
12
+ {{body}}
13
+ </body>
14
+ </html>`
15
+
16
+ ///////////////////////////////////////////////////////////
17
+
18
+ globalThis.COMMENT_FORM = `
19
+ <comdiv class="comment-block" id="ds-{{count}}">
20
+ <a name="cmt.{{count}}"/>
21
+ <div class="sys-comment-block" id="b-{{id}}">
22
+ <div class="comment-header">
23
+ <p>
24
+ <span class="comment-author" id="c{{id}}">
25
+ <a class="comment-authorurl" href="{{profile}}" title="Meet her">{{avatar}}</a>
26
+ <b><a class="comment-authorname" href="{{link}}" timestamp="{{timestamp}}" id="is-post-{{id}}">{{author}}</a></b>
27
+ <span class="comment-count" style="color: #FF9966;" id="cs-post-{{id}}">({{count}})</span>
28
+ <span class="comment-authorinfo" id="as-post-{{id}}">{{info}}</span>
29
+ </span>
30
+ </p>
31
+ </div>
32
+ <div class="comment-body">
33
+ <p>
34
+ <span class="comment-text" id="ss-post-{{id}}">{{content}}</span>
35
+ </p>
36
+ </div>
37
+ <div class="comment-footer">
38
+ <p>
39
+ <a class="comment-mark" href="#page_top"><img border="0" width="12" height="12" src="https://cdn.jsdelivr.net/gh/asinerum/project/team/gui/mark.gif"/></a>
40
+ <span class="comment-timestamp" style="color: #AAAAAA" id="ds-post-{{id}}">{{dated}}</span>
41
+ </p>
42
+ </div>
43
+ </div>
44
+ </comdiv>`
45
+
46
+ globalThis.COMMENT_DATA = {
47
+ count: 0,
48
+ id: '',
49
+ link: '',
50
+ author: '',
51
+ profile: '',
52
+ content: '',
53
+ timestamp: '',
54
+ dated: '',
55
+ avatar: '&#9673;',
56
+ info: '&#10003;',
57
+ }
58
+
59
+ ///////////////////////////////////////////////////////////
60
+
61
+ globalThis.POST_FORM = `
62
+ <div class="post-entry" id="AhttBlogspotComments" style="text-align: justify; width: 100%; overflow-wrap: break-word;">
63
+ <p>
64
+ <a class="post-title" id="bloggerEntryURL" href="{{entry}}">
65
+ <h2 class="post-entry-title" style="color: blue;" id="bloggerEntryTitle">{{title}}</h2>
66
+ </a>
67
+ </p>
68
+ <p>
69
+ <h3 class="post-author" style="color: blue;" id="bloggerEntryAuthor">{{author}}</h3>
70
+ <h5 class="post-info" style="color: blue;">
71
+ <span class="post-label" id="bloggerEntryLabel">{{label}}</span>
72
+ <span class="post-stamp" id="bloggerEntryGMTDate">{{stamp}}</span>
73
+ </h5>
74
+ </p>
75
+ <p>
76
+ <span class="post-entry-content" id="bloggerEntryContent">
77
+ {{content}}
78
+ </span>
79
+ </p>
80
+ <p>
81
+ <a name="comments-show"></a>
82
+ <h2 class="post-comments-info" style="color: blue;">
83
+ <a class="post-comments-notice" id="bloggerCommentCountingNotice" href="{{compage}}"><span class="post-comments" id="bloggerEntryTotalComments">{{total}}</span>&nbsp;Comments</a>
84
+ <span class="post-comments-count">(Page <span class="post-comments-page" id="bloggerCommentPageNo">{{pageno}}</span>/<span class="post-comments-pages" id="bloggerTotalCommentPages">{{pages}}</span>)</span>
85
+ <span class="post-comments-link" id="bloggerEntryCommentsLink">{{comlink}}</span>
86
+ </h2>
87
+ </p>
88
+ <p>
89
+ <span class="post-comments-container" id="bloggerEntryComment">
90
+ {{comments}}
91
+ </span>
92
+ </p>
93
+ </div>`
94
+
95
+ globalThis.POST_DATA = {
96
+ entry: '',
97
+ title: '',
98
+ author: '',
99
+ label: '',
100
+ stamp: '',
101
+ total: 0,
102
+ pageno: 0,
103
+ pages: 0,
104
+ content: '',
105
+ comments: '',
106
+ compage: '',
107
+ comlink: '',
108
+ }
109
+
110
+ ///////////////////////////////////////////////////////////
package/index.js ADDED
@@ -0,0 +1 @@
1
+ exports.hf = require('./htmls.js');
package/loadlib.js ADDED
@@ -0,0 +1,154 @@
1
+ console.log('Loading libs..');
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const hf = require('./htmls.js');
7
+ const pubvar = require('./blogger.pubvar.gd3.js');
8
+ const frmvar = require('./blogger.format.gd3.js');
9
+ const memvar = require('./buasc.js');
10
+
11
+ // Enable DOM global objects
12
+ // This may be in conflict with GoogleAPIs
13
+ const jd = require('jsdom-global')();
14
+
15
+ // Enable <DOMParser> class
16
+ const {JSDOM} = require('jsdom');
17
+ const jsdom = new JSDOM(`<!DOCTYPE html>`);
18
+ const {window} = jsdom;
19
+
20
+ /////////////////////////////////////////////////////////////////////////////
21
+
22
+ globalThis.DOMParser = window.DOMParser;
23
+
24
+ globalThis.DEF_HIDE_STAMPS = false;
25
+ globalThis.DEF_HIDE_COUNTS = false;
26
+
27
+ /////////////////////////////////////////////////////////////////////////////
28
+
29
+ globalThis.getArchivePostData = function (domdoc, timezone=7) {
30
+ let data = {};
31
+ data.entry = getAttribute(domdoc, {sel: 'id', sid: 'bloggerEntryURL', attr: 'href'});
32
+ data.title = getAttribute(domdoc, {sel: 'id', sid: 'bloggerEntryTitle'});
33
+ data.author = getAttribute(domdoc, {sel: 'id', sid: 'bloggerEntryAuthor'});
34
+ data.label = getAttribute(domdoc, {sel: 'id', sid: 'bloggerEntryLabel'});
35
+ data.stamp = getAttribute(domdoc, {sel: 'id', sid: 'bloggerEntryGMTDate'});
36
+ data.total = getAttribute(domdoc, {sel: 'id', sid: 'bloggerEntryTotalComments'}) || 0;
37
+ data.pages = getAttribute(domdoc, {sel: 'id', sid: 'bloggerTotalCommentPages'}) || 1;
38
+ data.pageno = getAttribute(domdoc, {sel: 'id', sid: 'bloggerCommentPageNo'}) || 1;
39
+ data.content = getAttribute(domdoc, {sel: 'id', sid: 'bloggerEntryContent', attr: 'innerHTML'});
40
+ data.compage = getAttribute(domdoc, {sel: 'id', sid: 'bloggerCommentCountingNotice', attr: 'href'});
41
+ data.comlink = getAttribute(domdoc, {sel: 'id', sid: 'bloggerEntryCommentsLink', attr: 'innerHTML'});
42
+ data.stamp = data.stamp || getAttribute(domdoc, {sel: 'id', sid: 'bloggerEntryHanoiDate'}) + `+${timezone}`;
43
+ data.label = data.label || '&#9730;'; // Umbrella web symbol
44
+ data.content = removeFontSize(data.content).cleanbrtags();
45
+ data.comments = ''; // Be filled later
46
+ return data
47
+ }
48
+
49
+ globalThis.getArchiveCommentsData = function (comdiv) {
50
+ let data = {};
51
+ data.count = getAttribute(comdiv, {sel: 'tag', sid: 'a', attr: 'name'}).split('.').pop();
52
+ const id1 = getAttribute(comdiv, {sid: 'comment-header', attr: 'id'});
53
+ const id2 = getAttribute(comdiv, {sid: 'comment-text', attr: 'id'});
54
+ data.id = (id1 || id2)?.split('-').pop();
55
+ data.author = getAttribute(comdiv, {sid: 'comment-authorname'});
56
+ data.profile = getAttribute(comdiv, {sid: 'comment-authorurl', attr: 'href'});
57
+ data.profile_id = data.profile ? data.profile.split('/').slice(-1).pop() : '';
58
+ data.avatar_url = getAttribute(comdiv, {sid: 'comment-avatar', attr: 'src'});
59
+ data.link = getAttribute(comdiv, {sid: 'comment-authorname', attr: 'href'});
60
+ data.dated = getAttribute(comdiv, {sid: 'comment-authorname', attr: 'alt'}); // VERY OLD STYLE
61
+ const stamp1 = getAttribute(comdiv, {sid: 'comment-header', attr: 'timestamp'}); // SINCE 2014
62
+ const stamp2 = getAttribute(comdiv, {sid: 'comment-authorname', attr: 'timestamp'}); // LATEST
63
+ data.timestamp = stamp1 || stamp2 || data.dated?.convertTime(); // PICK THIS BEFORE data.dated
64
+ if (!data.dated) data.dated = getAttribute(comdiv, {sid: 'comment-timestamp'}); // LATEST TIME
65
+ var reply = getAttribute(comdiv, {sid: 'reply-text', attr: 'innerHTML'});
66
+ reply = reply ? `\n\n<blockquote>${reply.cleanrepbrtags().cleanrepbrtags()}</blockquote>\n\n` : '';
67
+ data.content = getAttribute(comdiv, {sid: 'comment-text', attr: 'innerHTML'}).cleanbrtags() + reply;
68
+ data.content = removeFontSize(data.content).cleanrepptags();
69
+ data.info = ''; // Be filled later
70
+ return data
71
+ }
72
+
73
+ globalThis.getArchivePageData = function (domdoc, comdivs) {
74
+ let post = getArchivePostData(domdoc);
75
+ let coms = [];
76
+ for (const comdiv of comdivs) {
77
+ let com = getArchiveCommentsData(comdiv);
78
+ com.info = showVIP(com.profile, 1, 24, '&nbsp;', 1, com.count, com.id, post.entry);
79
+ com.content = getStyledComment(com.profile, com.author, com.timestamp, com.content, false);
80
+ // com.content = com.content.cmt2html();
81
+ coms.push(com);
82
+ }
83
+ post.comments = coms;
84
+ return post;
85
+ }
86
+
87
+ globalThis.convertComments = function (commentObject) {
88
+ commentObject.avatar = commentObject.avatar_url.convertAvatarUrl();
89
+ return commentObject.content.generateComment(cloneObject(commentObject));
90
+ }
91
+
92
+ String.prototype.convertArchiveData = function (sid='comment-block') {
93
+ const domdoc = this.domdoc();
94
+ const comdata = this.pickContent().domdoc();
95
+ const comdivs = comdata.getElementsByClassName(sid);
96
+ return getArchivePageData(domdoc, comdivs);
97
+ }
98
+
99
+ String.prototype.convertArchiveHtml = function (head=HTML_STYLE) {
100
+ let data = this.convertArchiveData();
101
+ if (!data.comlink) data.comlink = '';
102
+ data.comments = data.comments.map(item => convertComments(item)).join('')+'\n';
103
+ return data.content.generatePost(cloneObject(data)).generateHtml(head);
104
+ }
105
+
106
+ /////////////////////////////////////////////////////////////////////////////
107
+
108
+ // Ref. String.prototype.domdoc();
109
+ String.prototype.domdoc2 = function () {
110
+ return this.window().document;
111
+ }
112
+
113
+ // Reserved for old versions
114
+ String.prototype.comdiv = function (profunc='cmt2html') {
115
+ return this.correctContent(profunc);
116
+ }
117
+
118
+ String.prototype.void = function () { return this; }
119
+ String.prototype.jsdom = function () { return new JSDOM(this); }
120
+ String.prototype.window = function () { return this.jsdom().window; }
121
+ String.prototype.getdeid = function (deid) { return this.window().document.getElementById(deid).innerHTML; }
122
+ String.prototype.getisel = function (tag, index=0) { return this.window().document.querySelector(tag)[index].innerHTML; }
123
+
124
+ /////////////////////////////////////////////////////////////////////////////
125
+
126
+ String.prototype.createFolder = function () {
127
+ // [this] can be a slash-separated string aka nested folder
128
+ const folderPath = path.join(process.cwd(), this.toString());
129
+ try {
130
+ fs.mkdirSync(folderPath, {recursive: true});
131
+ return true;
132
+ } catch (err) {
133
+ return false;
134
+ }
135
+ }
136
+
137
+ String.prototype.generateHtml = function (head=HTML_STYLE, form=HTML_FORM) {
138
+ return form.parse({head, body: this});
139
+ }
140
+
141
+ String.prototype.generateComment = function (objComment={}, form=COMMENT_FORM) {
142
+ objComment.content = this.toString();
143
+ return form.parse(objComment);
144
+ }
145
+
146
+ String.prototype.generatePost = function (objPost={}, form=POST_FORM) {
147
+ objPost.content = this.toString();
148
+ return form.parse(objPost);
149
+ }
150
+
151
+ // Global usage:
152
+ // require('./loadlib.js');
153
+
154
+ /////////////////////////////////////////////////////////////////////////////
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "bloggerize",
3
+ "version": "1.0.0",
4
+ "description": "Tiny Blogger Archiving Toolkit",
5
+ "main": "index.js",
6
+ "dependencies": {
7
+ "cheerio": "^1.1.2",
8
+ "googleapis": "^169.0.0",
9
+ "node-fetch": "^3.3.2"
10
+ },
11
+ "devDependencies": {
12
+ "jsdom": "^27.4.0",
13
+ "jsdom-global": "^3.0.2"
14
+ },
15
+ "scripts": {
16
+ "test": "node test/script.js"
17
+ },
18
+ "keywords": [],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/asinerum/bloggerize.git"
22
+ },
23
+ "author": "Asinerum Project <https://github.com/asinerum>",
24
+ "license": "MIT"
25
+ }
package/test/script.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+
3
+ This tiny script creates Google Access Token for you to launch Blogger API tasks.
4
+ You have to do the following steps at Google Cloud Console:
5
+ 1. Create new project, pick it when done, or pick another existing project;
6
+ 2. Enable Blogger API V3 service for the project;
7
+ 3. Go to IAM tab on Project Settings menu. Review all project's users;
8
+ 4. Add new user for the project, pick it, or pick any existing user;
9
+ 5. Create and download Credentials JSON file of the picked user;
10
+ Then do these steps at your home machine:
11
+ 1. Save the downloaded file "credentials.json" in your working folder;
12
+ 2. Run this script. Follow its prompts;
13
+ 3. Wait for the file named "token.json" being created;
14
+ 4. Two files "credentials.json" and "token.json" must be kept safely.
15
+
16
+ */
17
+
18
+ const blogger = require('bloggerize/upload');
19
+
20
+ blogger.authorizeThru((res) => {
21
+ if (res) console.log('Done');
22
+ else { console.log('An error has occured'); }
23
+ });
package/upload.js ADDED
@@ -0,0 +1,226 @@
1
+ import {} from './blogger.format.gd3.js';
2
+
3
+ import { default as fs } from 'fs';
4
+
5
+ import * as cheerio from 'cheerio';
6
+
7
+ // Google API >< JSDOM conflicts
8
+ import { google } from 'googleapis';
9
+
10
+ const TOKEN_PATH = './token.json';
11
+ const APIKEY_PATH = './apikey.json';
12
+ const CREDENTIALS_PATH = './credentials.json';
13
+ const SCOPES = ['https://www.googleapis.com/auth/blogger'];
14
+
15
+ export const BLOG_ID = '8254806400148362373';
16
+ export const BLOG_URL = 'https://comment-archive.blogspot.com';
17
+
18
+ export async function rlInterface () {
19
+ const readline = await import('readline');
20
+ return readline.createInterface({
21
+ input: process.stdin,
22
+ output: process.stdout,
23
+ terminal: false,
24
+ });
25
+ }
26
+
27
+ export function getToken (oAuth2Client, code, {cbf=console.log, token_path=TOKEN_PATH}={}) {
28
+ oAuth2Client.getToken(code, (err, token) => {
29
+ if (err) return console.error('Error retrieving access token:', err.message);
30
+ oAuth2Client.setCredentials(token);
31
+ fs.writeFileSync(token_path, JSON.stringify(token));
32
+ console.log('Token stored to:', token_path);
33
+ cbf(oAuth2Client);
34
+ });
35
+ }
36
+
37
+ // Get new token if none exists
38
+ // Just publish Google Cloud project of the credentials to prevent token expiration
39
+ export async function getNewToken (oAuth2Client, cbf=console.log, {token_path=TOKEN_PATH, scopes=SCOPES}={}) {
40
+ const authUrl = oAuth2Client.generateAuthUrl({
41
+ access_type: 'offline',
42
+ prompt: 'consent',
43
+ scope: scopes,
44
+ });
45
+ console.log('Authorize this app by visiting this URL:\n\n', authUrl, '\n\n');
46
+ const rl = await rlInterface();
47
+ rl.question('Enter the code from that page here:\n', function (code) {
48
+ getToken(oAuth2Client, code.trim(), {cbf, token_path});
49
+ rl.close();
50
+ });
51
+ }
52
+
53
+ // Google Blogger API must be enabled in Google Cloud project
54
+ // oAuth2 credentials got at https://console.cloud.google.com/apis/dashboard?project=XXX
55
+ export function authorize (token_path=TOKEN_PATH, credentials_path=CREDENTIALS_PATH) {
56
+ let oAuth2Client;
57
+ try {
58
+ const credentials = JSON.parse(fs.readFileSync(credentials_path));
59
+ const { client_id, client_secret, redirect_uris } = credentials.installed;
60
+ oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
61
+ } catch (err) {
62
+ console.error('Error loading credentials:', err.message);
63
+ return [null, null];
64
+ }
65
+ try {
66
+ oAuth2Client.setCredentials(JSON.parse(fs.readFileSync(token_path)));
67
+ return [oAuth2Client, true];
68
+ } catch (err) {
69
+ console.error('Error loading client token:', err.message);
70
+ return [oAuth2Client, false];
71
+ }
72
+ }
73
+
74
+ export async function authorizeThru (cbf=console.log, {token_path=TOKEN_PATH, credentials_path=CREDENTIALS_PATH}={}) {
75
+ const [oAuth2Client, tokenpass] = authorize(token_path, credentials_path);
76
+ if (!oAuth2Client) return cbf(null);
77
+ if (!tokenpass) return await getNewToken(oAuth2Client, cbf, {token_path});
78
+ cbf(oAuth2Client);
79
+ }
80
+
81
+ // Delete Blogger posts in a date range
82
+ // start_date/end_date format: YYYY-MM-DD
83
+ export async function deletePosts (start_date, end_date, {blog_id=BLOG_ID, maxResults=500}={}) {
84
+ const [auth, tokenpass] = authorize();
85
+ if (!auth || !tokenpass) {
86
+ return console.log('Failed to get client credentials');
87
+ }
88
+ const blogger = google.blogger({ version: 'v3', auth });
89
+ try {
90
+ const blogRes = await blogger.blogs.get({ blogId: blog_id });
91
+ const blogId = blogRes.data.id;
92
+ console.log('Blog ID resolved:', blogId);
93
+ const postsRes = await blogger.posts.list({
94
+ blogId,
95
+ maxResults,
96
+ startDate: start_date,
97
+ endDate: end_date,
98
+ orderBy: 'published',
99
+ fetchBodies: false
100
+ });
101
+ const posts = postsRes.data.items || [];
102
+ console.log(`Found ${posts.length} posts`);
103
+ const postsToDelete = posts; // May use filter()
104
+ console.log(`Deleting ${postsToDelete.length} posts..`);
105
+ for (const post of postsToDelete) {
106
+ await blogger.posts.delete({ blogId, postId: post.id });
107
+ console.log(`Deleted post: ${post.title} (${post.id})`);
108
+ }
109
+ console.log('Done');
110
+ } catch (err) {
111
+ console.error('Error:', err.message);
112
+ }
113
+ }
114
+
115
+ // Post HTML file content to Blogger
116
+ export async function insertPosts (files, draft=false, blog_id=BLOG_ID, blog_url=BLOG_URL) {
117
+ const [auth, tokenpass] = authorize();
118
+ if (!auth || !tokenpass) {
119
+ return console.log('Failed to get client credentials');
120
+ }
121
+ const blogger = google.blogger({ version: 'v3', auth });
122
+ for (const file of files) {
123
+ let text, head;
124
+ try {
125
+ const content = fs.readFileSync(file, 'utf8');
126
+ [head, text] = await domGetArchivePostData(content);
127
+ } catch (err) {
128
+ console.error('File Error:', err.message);
129
+ continue;
130
+ }
131
+ const postData = text.bloggerPostData(
132
+ blog_id,
133
+ head.title,
134
+ { tid: ':P', page: head.pageno, labels: [head.label], dated: head.stamp.slice(0, 10) }
135
+ );
136
+ // (await postNotExist2(postData.published, postData.title, blog_url))
137
+ if (await postNotExist(postData.published, postData.title, blog_id)) {
138
+ blogger.posts.insert({
139
+ blogId: blog_id,
140
+ requestBody: postData,
141
+ isDraft: draft,
142
+ }, (err, res) => {
143
+ if (err) {
144
+ console.error('File:', file);
145
+ console.error('Post:', postData.title);
146
+ console.error('API Error:', err.message);
147
+ return;
148
+ }
149
+ console.log('Published:', res.data.url);
150
+ });
151
+ } else {
152
+ console.log('Ignored existing post:', postData.title);
153
+ }
154
+ }
155
+ }
156
+
157
+ export async function domGetArchivePostData (html, timezone=7, labicon='&#9730;') {
158
+ const text = html.pickContent('<body>', '</body>', false);
159
+ // const cheerio = await import('cheerio');
160
+ const $ = cheerio.load(text);
161
+ const head = {};
162
+ head.title = $('#bloggerEntryTitle').first().text().trim();
163
+ head.label = $('#bloggerEntryLabel').first().text().trim();
164
+ head.stamp = $('#bloggerEntryGMTDate').first().text().trim();
165
+ head.stampHn = $('#bloggerEntryHanoiDate').first().text().trim();
166
+ head.pageno = $('#bloggerCommentPageNo').first().text().trim();
167
+ head.stamp = head.stamp || head.stampHn + `+${timezone}`;
168
+ head.label = head.label || labicon;
169
+ return [head, text];
170
+ }
171
+
172
+ // Get posts between date range using GoogleAPIs with API Key
173
+ // This is not the same function as the one at <download.getPosts>
174
+ export async function getPosts (fromdate, {todate=null, url=BLOG_URL, blog=BLOG_ID, apikey_path=APIKEY_PATH}={}) {
175
+ todate = (todate||fromdate).convertDate() + 'T23:59:59Z';
176
+ fromdate = fromdate.convertDate() + 'T00:00:00Z';
177
+ const apikey = JSON.parse(fs.readFileSync(apikey_path)).key;
178
+ const feedUrl = `https://www.googleapis.com/blogger/v3/blogs/${blog}/posts?maxResults=500&startDate=${fromdate}&endDate=${todate}&key=${apikey}`;
179
+ const data = await loadJson(feedUrl);
180
+ const entries = data.items || [];
181
+ const posts = [];
182
+ for (const entry of entries) {
183
+ posts.push({
184
+ id: entry.id,
185
+ title: entry.title,
186
+ label: entry.labels?.at(0) || '',
187
+ entry: entry.url,
188
+ stamp: entry.published,
189
+ author: entry.author?.displayName,
190
+ summary: '', // Can be filled later
191
+ total: entry.replies?.totalItems,
192
+ pages: 0,
193
+ pageno: 0,
194
+ comments: [],
195
+ compage: '',
196
+ comlink: '',
197
+ content: entry.content,
198
+ });
199
+ }
200
+ return posts;
201
+ }
202
+
203
+ // Check posts title using GoogleAPIs with API Key
204
+ export async function postNotExist (dated, title, blog=BLOG_ID, apikey_path=APIKEY_PATH) {
205
+ dated = dated.slice(0, 10);
206
+ const fromdate = dated + 'T00:00:00Z';
207
+ const todate = dated + 'T23:59:59Z';
208
+ const apikey = JSON.parse(fs.readFileSync(apikey_path)).key;
209
+ const feedUrl = `https://www.googleapis.com/blogger/v3/blogs/${blog}/posts?maxResults=50&startDate=${fromdate}&endDate=${todate}&key=${apikey}`;
210
+ const data = await loadJson(feedUrl);
211
+ const entries = data.items || [];
212
+ for (const entry of entries) if (entry.title == title) return false;
213
+ return true;
214
+ }
215
+
216
+ // Check posts title using Blogger Atom with no API Key
217
+ export async function postNotExist2 (dated, title, url=BLOG_URL) {
218
+ dated = dated.slice(0, 10);
219
+ const fromdate = dated + 'T00:00:00Z';
220
+ const todate = dated + 'T23:59:59Z';
221
+ const feedUrl = `${url}/feeds/posts/summary?alt=json&redirect=false&max-results=200&published-min=${fromdate}&published-max=${todate}`;
222
+ const data = await loadJson(feedUrl);
223
+ const entries = data?.feed?.entry;
224
+ if (Array.isArray(entries)) for (const entry of entries) if (entry.title?.$t == title) return false;
225
+ return true;
226
+ }