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/LICENSE.txt +21 -0
- package/README.md +19 -0
- package/blogger.format.gd3.js +952 -0
- package/blogger.pubvar.gd3.js +193 -0
- package/buasc.js +81 -0
- package/download.js +182 -0
- package/examples/apikey.json +1 -0
- package/examples/credentials.json +0 -0
- package/examples/delete.js +29 -0
- package/examples/download.js +63 -0
- package/examples/login.js +23 -0
- package/examples/token.json +0 -0
- package/examples/upload.js +72 -0
- package/htmls.js +110 -0
- package/index.js +1 -0
- package/loadlib.js +154 -0
- package/package.json +25 -0
- package/test/script.js +23 -0
- package/upload.js +226 -0
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: '◉',
|
|
56
|
+
info: '✓',
|
|
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> 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 || '☂'; // 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, ' ', 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='☂') {
|
|
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
|
+
}
|