backend-manager 3.2.103 → 3.2.104

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.
@@ -0,0 +1,215 @@
1
+ const fetch = require('node-fetch');
2
+ const wonderfulFetch = require('wonderful-fetch');
3
+ const Poster = require('ultimate-jekyll-poster');
4
+ const pathApi = require('path');
5
+ const { get } = require('lodash');
6
+
7
+ function Module() {
8
+
9
+ }
10
+
11
+ Module.prototype.main = function () {
12
+ const self = this;
13
+ const Manager = self.Manager;
14
+ const Api = self.Api;
15
+ const assistant = self.assistant;
16
+ const payload = self.payload;
17
+
18
+ return new Promise(async function(resolve, reject) {
19
+ // Perform checks
20
+ if (!payload.user.roles.admin && !payload.user.roles.blogger) {
21
+ return reject(assistant.errorify(`Admin required.`, {code: 401}));
22
+ }
23
+
24
+ // Get repo info
25
+ const repoInfo = assistant.parseRepo(get(self.Manager.config, 'github.repo_website'));
26
+ const poster = new Poster();
27
+
28
+ assistant.log(`main(): Creating post...`, repoInfo);
29
+
30
+ // Set defaults
31
+ const githubUser = get(self.Manager.config, 'github.user');
32
+ const githubKey = get(self.Manager.config, 'github.key');
33
+
34
+ // Save to disk OR commit
35
+ poster.onDownload = function (meta) {
36
+ return new Promise(async function(resolve, reject) {
37
+ const tempPath = meta.tempPath;
38
+ const finalPath = poster.removeDirDot(meta.finalPath);
39
+
40
+ // Log
41
+ assistant.log(`onDownload(): tempPath`, tempPath);
42
+ assistant.log(`onDownload(): finalPath`, finalPath);
43
+
44
+ // Check for missing paths (need to check for meta.finalPath as well because it's not always set)
45
+ if (!tempPath || !finalPath || !meta.finalPath) {
46
+ return reject(new Error(`onDownload(): tempPath or finalPath is missing`));
47
+ }
48
+
49
+ // Save to disk
50
+ poster.readImage(tempPath)
51
+ .then(image => {
52
+ self.createFile(githubUser, repoInfo.user, repoInfo.name, githubKey, finalPath, image)
53
+ .then(() => {resolve()})
54
+ .catch((e) => {reject(e)})
55
+ })
56
+ .catch((e) => {reject(e)})
57
+
58
+ });
59
+ }
60
+
61
+ // Create post
62
+ const finalPost = await poster.create(payload.data).catch(e => e);
63
+ if (finalPost instanceof Error) {
64
+ return reject(assistant.errorify(`Failed to post: ${finalPost}`, {code: 500}));
65
+ }
66
+
67
+ // Request indexing
68
+ // DEPRECATED
69
+ // try {
70
+ // const url = get(self.Manager.config, 'brand.url');
71
+ // const encoded = encodeURIComponent(`${url}/sitemap.xml`);
72
+
73
+ // wonderfulFetch(`https://www.google.com/ping?sitemap=${encoded}`)
74
+
75
+ // // TODO
76
+ // // https://developers.google.com/search/apis/indexing-api/v3/prereqs
77
+ // // https://developers.google.com/search/apis/indexing-api/v3/using-api#url
78
+ // } catch (e) {
79
+ // assistant.error(`Failed to ping google: ${e}`);
80
+ // }
81
+
82
+ // Set file path and content
83
+ const filePath = poster.removeDirDot(finalPost.path);
84
+ const fileContent = finalPost.content;
85
+
86
+ // Save post OR commit
87
+ await self.createFile(githubUser, repoInfo.user, repoInfo.name, githubKey, filePath, fileContent)
88
+ .then(() => {
89
+ return resolve({data: finalPost});
90
+ })
91
+ .catch((e) => {
92
+ return reject(assistant.errorify(`Failed to post: ${e}`, {code: 500}));
93
+ })
94
+
95
+ });
96
+
97
+ };
98
+
99
+ // HELPERS //
100
+ Module.prototype.createFile = function (user, repoUser, repoName, key, path, contents) {
101
+ const self = this;
102
+ const Manager = self.Manager;
103
+ const Api = self.Api;
104
+ const assistant = self.assistant;
105
+ const payload = self.payload;
106
+
107
+ return new Promise(async (resolve, reject) => {
108
+ let fileParsed = pathApi.parse(path);
109
+ let base64Data = Buffer.from(contents).toString('base64');
110
+ let sha;
111
+
112
+ // Log
113
+ assistant.log(`createFile(): Writing file to ${repoUser}/${repoName}/${path}`);
114
+
115
+ // Try to get sha
116
+ try {
117
+ let branch = repoName === 'ultimate-jekyll' ? 'template' : 'master';
118
+ let pathGet = `https://api.github.com/repos/${repoUser}/${repoName}/git/trees/${branch}:${encodeURIComponent(pathApi.dirname(path))}`;
119
+
120
+ // Log
121
+ assistant.log(`createFile(): pathGet`, pathGet);
122
+
123
+ // Make request
124
+ await makeRequest({
125
+ method: 'GET',
126
+ url: pathGet,
127
+ body: {
128
+ },
129
+ timeout: 30000,
130
+ json: true,
131
+ headers: {
132
+ 'User-Agent': user,
133
+ // 'Authorization': `Basic ${user}:${key}`,
134
+ 'Authorization': `Basic ${Buffer.from(user + ':' + key).toString('base64')}`,
135
+ }
136
+ })
137
+ .then(function (resp) {
138
+ // sha = resp.sha;
139
+ sha = resp.tree.find(function (element) {
140
+ // console.log('checiing', element.path, fileParsed.base);
141
+ return element.path === fileParsed.base;
142
+ });
143
+ sha = sha.sha;
144
+ });
145
+ } catch (e) {
146
+ sha = null;
147
+ }
148
+
149
+ let pathPut = `https://api.github.com/repos/${repoUser}/${repoName}/contents/${path}`;
150
+ let writeRequest = {
151
+ // url: `https://api.github.com/repos/:owner/:repo/contents/:path`,
152
+ method: 'PUT',
153
+ url: pathPut,
154
+ body: {
155
+ message: `BackendManager Post: ${new Date().toISOString()}`,
156
+ content: base64Data,
157
+ },
158
+ timeout: 30000,
159
+ json: true,
160
+ headers: {
161
+ 'User-Agent': user,
162
+ // 'Authorization': `Basic ${user}:${key}`,
163
+ 'Authorization': `Basic ${Buffer.from(user + ':' + key).toString('base64')}`,
164
+ }
165
+ }
166
+
167
+ // Log
168
+ assistant.log(`createFile(): pathPut`, pathPut);
169
+
170
+ // Add sha if it exists
171
+ if (sha) {
172
+ writeRequest.body.sha = sha;
173
+ }
174
+
175
+ // Make request
176
+ await makeRequest(writeRequest)
177
+ .then((json) => {
178
+ if (!json || (json.message && (json.message === 'Not Found' || json.message.includes('Invalid request'))) ) {
179
+ return reject(new Error(json.message));
180
+ }
181
+ })
182
+ .catch((e) => {
183
+ return reject(e);
184
+ })
185
+ return resolve(true)
186
+ });
187
+ }
188
+
189
+ function makeRequest(options) {
190
+ return new Promise(function(resolve, reject) {
191
+ options.headers = options.headers || {};
192
+ options.headers['Content-Type'] = 'application/json';
193
+
194
+ let hasBody = Object.keys(options.body || {}).length > 0
195
+
196
+ fetch(options.url, {
197
+ method: options.method,
198
+ body: hasBody ? JSON.stringify(options.body) : undefined,
199
+ timeout: 30000,
200
+ headers: options.headers,
201
+ auth: options.auth,
202
+ })
203
+ .then(res => res.json())
204
+ .then(json => {
205
+ return resolve(json);
206
+ })
207
+ .catch(e => {
208
+ // console.error('e', e);
209
+ return reject(e);
210
+ })
211
+
212
+ });
213
+ }
214
+
215
+ module.exports = Module;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "3.2.103",
3
+ "version": "3.2.104",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -53,6 +53,7 @@
53
53
  "lodash": "^4.17.21",
54
54
  "lowdb": "^1.0.0",
55
55
  "mailchimp-api-v3": "^1.15.0",
56
+ "mime-types": "^2.1.35",
56
57
  "mocha": "^8.4.0",
57
58
  "moment": "^2.30.1",
58
59
  "nanoid": "^3.3.7",
@@ -68,7 +69,7 @@
68
69
  "uid-generator": "^2.0.0",
69
70
  "ultimate-jekyll-poster": "^1.0.1",
70
71
  "uuid": "^9.0.1",
71
- "wonderful-fetch": "^1.1.5",
72
+ "wonderful-fetch": "^1.1.8",
72
73
  "wonderful-log": "^1.0.5",
73
74
  "yargs": "^17.7.2"
74
75
  }
@@ -1,8 +1,19 @@
1
- const fetch = require('node-fetch');
2
- const wonderfulFetch = require('wonderful-fetch');
3
- const Poster = require('ultimate-jekyll-poster');
4
- const pathApi = require('path');
5
- const { get } = require('lodash');
1
+ const fetch = require('wonderful-fetch');
2
+ const moment = require('moment');
3
+ const jetpack = require('fs-jetpack');
4
+ const powertools = require('node-powertools');
5
+ const uuidv4 = require('uuid').v4;
6
+ const path = require('path');
7
+ const { Octokit } = require('@octokit/rest');
8
+
9
+ const POST_TEMPLATE = jetpack.read(`${__dirname}/templates/post.html`);
10
+ const IMAGE_TAG = `{%- include /master/helpers/blog-image.html name="{name}" alt="{alt}" -%}`;
11
+ const IMAGE_PATH_SRC = `assets/_src/images/blog/posts/post-{id}/`;
12
+ // const IMAGE_PATH_REG = `/assets/images/blog/posts/post-{id}/`;
13
+ const AD_TAG = `{% include /master/modules/adunits/adsense-in-article.html index="{index}" %}`;
14
+
15
+ const IMAGE_REGEX = /(?:!\[(.*?)\]\((.*?)\))/img;
16
+ const LINK_REGEX = /(?:\[(.*?)\]\((.*?)\))/img;
6
17
 
7
18
  function Module() {
8
19
 
@@ -16,200 +27,318 @@ Module.prototype.main = function () {
16
27
  const payload = self.payload;
17
28
 
18
29
  return new Promise(async function(resolve, reject) {
19
- // Perform checks
20
- if (!payload.user.roles.admin && !payload.user.roles.blogger) {
21
- return reject(assistant.errorify(`Admin required.`, {code: 401}));
22
- }
30
+ try {
31
+ // Perform checks
32
+ if (!payload.user.roles.admin && !payload.user.roles.blogger) {
33
+ return reject(assistant.errorify(`Admin required.`, {code: 401}));
34
+ }
35
+
36
+ // Log payload
37
+ assistant.log(`main(): payload.data`, payload.data);
23
38
 
24
- // Get repo info
25
- const repoInfo = assistant.parseRepo(get(self.Manager.config, 'github.repo_website'));
26
- const poster = new Poster();
39
+ // Set now
40
+ const now = assistant.meta.startTime.timestamp;
41
+ const bemRepo = assistant.parseRepo(Manager?.config?.github?.repo_website);
27
42
 
28
- assistant.log(`main(): Creating post...`, repoInfo);
43
+ // Setup Octokit
44
+ self.octokit = new Octokit({
45
+ auth: Manager?.config?.github?.key,
46
+ });
29
47
 
30
- // Set defaults
31
- const githubUser = get(self.Manager.config, 'github.user');
32
- const githubKey = get(self.Manager.config, 'github.key');
48
+ // Check for required values
49
+ if (!payload.data.payload.title) {
50
+ return reject(assistant.errorify(`Missing required parameter: title`, {code: 400}));
51
+ } else if (!payload.data.payload.url) {
52
+ return reject(assistant.errorify(`Missing required parameter: url`, {code: 400}));
53
+ } else if (!payload.data.payload.excerpt) {
54
+ return reject(assistant.errorify(`Missing required parameter: excerpt`, {code: 400}));
55
+ } else if (!payload.data.payload.headerImageURL) {
56
+ return reject(assistant.errorify(`Missing required parameter: headerImageURL`, {code: 400}));
57
+ } else if (!payload.data.payload.body) {
58
+ return reject(assistant.errorify(`Missing required parameter: body`, {code: 400}));
59
+ }
60
+
61
+ // Fix required values
62
+ payload.data.payload.url = payload.data.payload.url
63
+ // Replace /blog/ (slashes may or may not be present)
64
+ .replace(/blog\//ig, '')
65
+ // Remove leading and trailing slashes
66
+ .replace(/^\/|\/$/g, '');
67
+
68
+ // Fix body
69
+ payload.data.payload.body = payload.data.payload.body
70
+ // Replace heading text (# + payload.data.payload.title) (just the first instance in case it is repeated)
71
+ .replace(powertools.regexify(`/# ${payload.data.payload.title}/i`), '')
72
+ // Remove extra newlines
73
+ .replace(/\n\n\n+/g, '\n\n')
74
+ // Trim
75
+ .trim();
76
+
77
+ // Fix other values
78
+ payload.data.payload.author = payload.data.payload.author || 'alex';
79
+ payload.data.payload.affiliate = payload.data.payload.affiliate || '';
80
+ payload.data.payload.tags = payload.data.payload.tags || '';
81
+ payload.data.payload.categories = payload.data.payload.categories || '';
82
+
83
+ // Fix even more values
84
+ payload.data.payload.layout = payload.data.payload.layout || 'app/blog/post';
85
+ payload.data.payload.date = payload.data.payload.date || moment(now).format('YYYY-MM-DD');
86
+ payload.data.payload.id = payload.data.payload.id || Math.round(new Date(now).getTime() / 1000);
87
+ payload.data.payload.path = payload.data.payload.path || `_posts/${moment(now).format('YYYY')}/guest`;
88
+ payload.data.payload.githubUser = payload.data.payload.githubUser || bemRepo.user;
89
+ payload.data.payload.githubRepo = payload.data.payload.githubRepo || bemRepo.name;
33
90
 
34
- // Save to disk OR commit
35
- poster.onDownload = function (meta) {
36
- return new Promise(async function(resolve, reject) {
37
- const tempPath = meta.tempPath;
38
- const finalPath = poster.removeDirDot(meta.finalPath);
91
+ // Log
92
+ assistant.log(`main(): Creating post...`, payload.data.payload);
39
93
 
40
- // Log
41
- assistant.log(`onDownload(): tempPath`, tempPath);
42
- assistant.log(`onDownload(): finalPath`, finalPath);
94
+ // Extract all images
95
+ await self.extractImages();
43
96
 
44
- // Check for missing paths (need to check for meta.finalPath as well because it's not always set)
45
- if (!tempPath || !finalPath || !meta.finalPath) {
46
- return reject(new Error(`onDownload(): tempPath or finalPath is missing`));
47
- }
97
+ // Set defaults
98
+ const formattedContent = powertools.template(POST_TEMPLATE, payload.data.payload);
48
99
 
49
- // Save to disk
50
- poster.readImage(tempPath)
51
- .then(image => {
52
- self.createFile(githubUser, repoInfo.user, repoInfo.name, githubKey, finalPath, image)
53
- .then(() => {resolve()})
54
- .catch((e) => {reject(e)})
55
- })
56
- .catch((e) => {reject(e)})
100
+ // Insert ads
101
+ const articleWithAds = insertAds(formattedContent, 3, 1500);
57
102
 
58
- });
103
+
104
+
105
+ // Log
106
+ assistant.log(`main(): articleWithAds`, articleWithAds);
107
+
108
+ // Upload post
109
+ const uploadPost = await self.uploadPost(articleWithAds);
110
+
111
+ // Log
112
+ assistant.log(`main(): uploadPost`, uploadPost);
113
+
114
+ // Resolve
115
+ return resolve({data: payload.data.payload});
116
+ } catch (e) {
117
+ return reject(e);
59
118
  }
119
+ });
120
+ };
121
+
122
+ // Extract images
123
+ Module.prototype.extractImages = function () {
124
+ const self = this;
125
+ const Manager = self.Manager;
126
+ const Api = self.Api;
127
+ const assistant = self.assistant;
128
+ const payload = self.payload;
129
+
130
+ return new Promise(async function(resolve, reject) {
131
+ // Extract images
132
+ const matches = payload.data.payload.body.matchAll(IMAGE_REGEX);
133
+ const images = Array.from(matches).map(match => ({
134
+ full: match[0] || '',
135
+ src: match[2] || '',
136
+ alt: match[1] || uuidv4(),
137
+ replace: true,
138
+ }));
139
+
140
+ // Add heading image to beginning of images
141
+ images.unshift({
142
+ full: '',
143
+ src: payload.data.payload.headerImageURL,
144
+ alt: payload.data.payload.url,
145
+ replace: false,
146
+ });
60
147
 
61
- // Create post
62
- const finalPost = await poster.create(payload.data).catch(e => e);
63
- if (finalPost instanceof Error) {
64
- return reject(assistant.errorify(`Failed to post: ${finalPost}`, {code: 500}));
148
+ // Log
149
+ assistant.log(`extractImages(): images`, images);
150
+
151
+ // Check if no images
152
+ if (!images) {
153
+ return resolve();
65
154
  }
66
155
 
67
- // Request indexing
68
- // DEPRECATED
69
- // try {
70
- // const url = get(self.Manager.config, 'brand.url');
71
- // const encoded = encodeURIComponent(`${url}/sitemap.xml`);
72
-
73
- // wonderfulFetch(`https://www.google.com/ping?sitemap=${encoded}`)
74
-
75
- // // TODO
76
- // // https://developers.google.com/search/apis/indexing-api/v3/prereqs
77
- // // https://developers.google.com/search/apis/indexing-api/v3/using-api#url
78
- // } catch (e) {
79
- // assistant.error(`Failed to ping google: ${e}`);
80
- // }
81
-
82
- // Set file path and content
83
- const filePath = poster.removeDirDot(finalPost.path);
84
- const fileContent = finalPost.content;
85
-
86
- // Save post OR commit
87
- await self.createFile(githubUser, repoInfo.user, repoInfo.name, githubKey, filePath, fileContent)
88
- .then(() => {
89
- return resolve({data: finalPost});
90
- })
91
- .catch((e) => {
92
- return reject(assistant.errorify(`Failed to post: ${e}`, {code: 500}));
93
- })
156
+ // Loop through images
157
+ for (let index = 0; index < images.length; index++) {
158
+ const image = images[index];
94
159
 
95
- });
160
+ // Download image
161
+ const download = await self.downloadImage(image.src, image.alt).catch((e) => e);
162
+
163
+ // Log
164
+ assistant.log(`extractImages(): download`, download);
165
+
166
+ // Check for error
167
+ if (download instanceof Error) {
168
+ return reject(download);
169
+ }
170
+
171
+ // Upload image
172
+ const upload = await self.uploadImage(download).catch((e) => e);
173
+
174
+ // Log
175
+ assistant.log(`extractImages(): upload`, upload);
176
+
177
+ // Check for error
178
+ if (upload instanceof Error) {
179
+ return reject(upload);
180
+ }
96
181
 
182
+ // Create new image tag
183
+ const tag = powertools.template(IMAGE_TAG, {name: download.filename, alt: image.alt});
184
+
185
+ // Replace image in body
186
+ if (image.replace) {
187
+ payload.data.payload.body = payload.data.payload.body
188
+ .replace(image.full, tag);
189
+ }
190
+
191
+ // Log
192
+ assistant.log(`extractImages(): tag`, tag);
193
+ }
194
+
195
+ // Resolve
196
+ return resolve();
197
+ });
97
198
  };
98
199
 
99
- // HELPERS //
100
- Module.prototype.createFile = function (user, repoUser, repoName, key, path, contents) {
200
+ // Downlaod image
201
+ Module.prototype.downloadImage = function (src, alt) {
101
202
  const self = this;
102
203
  const Manager = self.Manager;
103
204
  const Api = self.Api;
104
205
  const assistant = self.assistant;
105
206
  const payload = self.payload;
106
207
 
107
- return new Promise(async (resolve, reject) => {
108
- let fileParsed = pathApi.parse(path);
109
- let base64Data = Buffer.from(contents).toString('base64');
110
- let sha;
208
+ return new Promise(async function(resolve, reject) {
209
+ // Log
210
+ const hyphenated = hyphenate(alt);
111
211
 
112
212
  // Log
113
- assistant.log(`createFile(): Writing file to ${repoUser}/${repoName}/${path}`);
213
+ assistant.log(`downloadImage(): src=${src}, alt=${alt}, hyphenated=${hyphenated}`);
114
214
 
115
- // Try to get sha
116
- try {
117
- let branch = repoName === 'ultimate-jekyll' ? 'template' : 'master';
118
- let pathGet = `https://api.github.com/repos/${repoUser}/${repoName}/git/trees/${branch}:${encodeURIComponent(pathApi.dirname(path))}`;
215
+ // Get image
216
+ await fetch(src, {
217
+ method: 'get',
218
+ download: `${assistant.tmpdir}/${hyphenated}`,
219
+ })
220
+ .then((r) => {
221
+ r.filename = path.basename(r.path);
222
+ r.ext = path.extname(r.path);
119
223
 
120
224
  // Log
121
- assistant.log(`createFile(): pathGet`, pathGet);
122
-
123
- // Make request
124
- await makeRequest({
125
- method: 'GET',
126
- url: pathGet,
127
- body: {
128
- },
129
- timeout: 30000,
130
- json: true,
131
- headers: {
132
- 'User-Agent': user,
133
- // 'Authorization': `Basic ${user}:${key}`,
134
- 'Authorization': `Basic ${Buffer.from(user + ':' + key).toString('base64')}`,
135
- }
136
- })
137
- .then(function (resp) {
138
- // sha = resp.sha;
139
- sha = resp.tree.find(function (element) {
140
- // console.log('checiing', element.path, fileParsed.base);
141
- return element.path === fileParsed.base;
142
- });
143
- sha = sha.sha;
144
- });
145
- } catch (e) {
146
- sha = null;
147
- }
225
+ assistant.log(`downloadImage(): Result`, r.path);
148
226
 
149
- let pathPut = `https://api.github.com/repos/${repoUser}/${repoName}/contents/${path}`;
150
- let writeRequest = {
151
- // url: `https://api.github.com/repos/:owner/:repo/contents/:path`,
152
- method: 'PUT',
153
- url: pathPut,
154
- body: {
155
- message: `BackendManager Post: ${new Date().toISOString()}`,
156
- content: base64Data,
157
- },
158
- timeout: 30000,
159
- json: true,
160
- headers: {
161
- 'User-Agent': user,
162
- // 'Authorization': `Basic ${user}:${key}`,
163
- 'Authorization': `Basic ${Buffer.from(user + ':' + key).toString('base64')}`,
164
- }
165
- }
227
+ // If not .jpg, reject
228
+ if (r.ext !== '.jpg') {
229
+ return reject(assistant.errorify(`Images must be .jpg (not ${r.ext})`, {code: 400}));
230
+ }
166
231
 
167
- // Log
168
- assistant.log(`createFile(): pathPut`, pathPut);
232
+ // Save image
233
+ return resolve(r);
234
+ })
235
+ .catch((e) => reject(e));
236
+ });
237
+ };
169
238
 
170
- // Add sha if it exists
171
- if (sha) {
172
- writeRequest.body.sha = sha;
173
- }
239
+ // Upload image to GitHub
240
+ Module.prototype.uploadImage = function (image) {
241
+ const self = this;
242
+ const Manager = self.Manager;
243
+ const Api = self.Api;
244
+ const assistant = self.assistant;
245
+ const payload = self.payload;
174
246
 
175
- // Make request
176
- await makeRequest(writeRequest)
177
- .then((json) => {
178
- if (!json || (json.message && (json.message === 'Not Found' || json.message.includes('Invalid request'))) ) {
179
- return reject(new Error(json.message));
180
- }
181
- })
182
- .catch((e) => {
183
- return reject(e);
184
- })
185
- return resolve(true)
247
+ return new Promise(async function(resolve, reject) {
248
+ // Save variables
249
+ const filepath = image.path;
250
+ const filename = image.filename;
251
+ const assetsPath = powertools.template(IMAGE_PATH_SRC, payload.data.payload);
252
+
253
+ // Log
254
+ assistant.log(`uploadImage(): image`, image);
255
+ assistant.log(`uploadImage(): path`, `${assetsPath}${filename}`);
256
+
257
+ // Upload image
258
+ await self.octokit.rest.repos.createOrUpdateFileContents({
259
+ owner: payload.data.payload.githubUser,
260
+ repo: payload.data.payload.githubRepo,
261
+ path: `${assetsPath}${filename}`,
262
+ message: `📦 admin:create-post:upload-image ${filename}`,
263
+ content: jetpack.read(filepath, 'buffer').toString('base64'),
264
+ })
265
+ .then((r) => {
266
+ // Log
267
+ assistant.log(`uploadImage(): Result`, r);
268
+
269
+ // Resolve
270
+ return resolve(r);
271
+ })
272
+ .catch((e) => reject(e));
186
273
  });
187
- }
274
+ };
188
275
 
189
- function makeRequest(options) {
190
- return new Promise(function(resolve, reject) {
191
- options.headers = options.headers || {};
192
- options.headers['Content-Type'] = 'application/json';
193
-
194
- let hasBody = Object.keys(options.body || {}).length > 0
195
-
196
- fetch(options.url, {
197
- method: options.method,
198
- body: hasBody ? JSON.stringify(options.body) : undefined,
199
- timeout: 30000,
200
- headers: options.headers,
201
- auth: options.auth,
202
- })
203
- .then(res => res.json())
204
- .then(json => {
205
- return resolve(json);
206
- })
207
- .catch(e => {
208
- // console.error('e', e);
209
- return reject(e);
210
- })
276
+ // Upload post to GitHub
277
+ Module.prototype.uploadPost = function (content) {
278
+ const self = this;
279
+ const Manager = self.Manager;
280
+ const Api = self.Api;
281
+ const assistant = self.assistant;
282
+ const payload = self.payload;
283
+
284
+ return new Promise(async function(resolve, reject) {
285
+ // Save variables
286
+ const filename = `${payload.data.payload.path}/${payload.data.payload.date}-${payload.data.payload.url}.md`;
211
287
 
288
+ // Log
289
+ assistant.log(`uploadPost(): filename`, filename);
290
+
291
+ // Upload post
292
+ await self.octokit.rest.repos.createOrUpdateFileContents({
293
+ owner: payload.data.payload.githubUser,
294
+ repo: payload.data.payload.githubRepo,
295
+ path: filename,
296
+ message: `📦 admin:create-post:upload-post ${filename}`,
297
+ content: Buffer.from(content).toString('base64'),
298
+ })
299
+ .then((r) => {
300
+ // Log
301
+ assistant.log(`uploadPost(): Result`, r);
302
+
303
+ // Resolve
304
+ return resolve(r);
305
+ })
306
+ .catch((e) => reject(e));
212
307
  });
308
+ };
309
+
310
+ function hyphenate(s) {
311
+ return s
312
+ // Remove everything that is not a letter or a number
313
+ .replace(/[^a-zA-Z0-9]/g, '-')
314
+ // Replace multiple hyphens with a single hyphen
315
+ .replace(/-+/g, '-')
316
+ // Remove leading and trailing hyphens
317
+ .replace(/^-|-$/g, '')
318
+ // Lowercase
319
+ .toLowerCase();
320
+ }
321
+
322
+ function insertAds(article, paragraphsPerAd, minCharsBetweenAds) {
323
+ const paragraphs = article.split('\n\n');
324
+ const newParagraphs = [];
325
+ let charCount = 0;
326
+ let adCount = 0;
327
+
328
+ for (let i = 0, total = paragraphs.length; i < total; i++) {
329
+ const collection = paragraphs[i];
330
+
331
+ charCount += collection.length;
332
+
333
+ newParagraphs.push(collection);
334
+
335
+ if (i >= paragraphsPerAd && charCount >= minCharsBetweenAds) {
336
+ newParagraphs.push(powertools.template(AD_TAG, {index: adCount++}));
337
+ charCount = 0;
338
+ }
339
+ }
340
+
341
+ return newParagraphs.join('\n\n');
213
342
  }
214
343
 
215
344
  module.exports = Module;
@@ -1,192 +0,0 @@
1
- const fetch = require('wonderful-fetch');
2
- const moment = require('moment');
3
- const jetpack = require('fs-jetpack');
4
- const powertools = require('node-powertools');
5
- const uuidv4 = require('uuid').v4;
6
-
7
- const POST_TEMPLATE = jetpack.read(`${__dirname}/templates/post.html`);
8
- const IMAGE_TAG = `{%- include /master/helpers/blog-image.html name="{name}" alt="{alt}" -%}`;
9
- const IMAGE_PATH_SRC = `./assets/_src/images/blog/posts/post-{id}/`;
10
- const IMAGE_PATH_REG = `/assets/images/blog/posts/post-{id}/`;
11
- const AD_TAG = `{% include /master/modules/adunits/adsense-in-article.html index="{index}" %}`;
12
-
13
- const IMAGE_REGEX = /(?:!\[(.*?)\]\((.*?)\))/img;
14
- const LINK_REGEX = /(?:\[(.*?)\]\((.*?)\))/img;
15
-
16
- function Module() {
17
-
18
- }
19
-
20
- Module.prototype.main = function () {
21
- const self = this;
22
- const Manager = self.Manager;
23
- const Api = self.Api;
24
- const assistant = self.assistant;
25
- const payload = self.payload;
26
-
27
- return new Promise(async function(resolve, reject) {
28
- // Perform checks
29
- if (!payload.user.roles.admin && !payload.user.roles.blogger) {
30
- return reject(assistant.errorify(`Admin required.`, {code: 401}));
31
- }
32
-
33
- // Log payload
34
- assistant.log(`main(): payload.data`, payload.data);
35
-
36
- // Set now
37
- const now = assistant.meta.startTime.timestamp;
38
- const bemRepo = assistant.parseRepo(Manager?.config?.github?.repo_website);
39
-
40
- // Check for required values
41
- if (!payload.data.payload.title) {
42
- return reject(assistant.errorify(`Missing required parameter: title`, {code: 400}));
43
- } else if (!payload.data.payload.url) {
44
- return reject(assistant.errorify(`Missing required parameter: url`, {code: 400}));
45
- } else if (!payload.data.payload.excerpt) {
46
- return reject(assistant.errorify(`Missing required parameter: excerpt`, {code: 400}));
47
- } else if (!payload.data.payload.headerImageURL) {
48
- return reject(assistant.errorify(`Missing required parameter: headerImageURL`, {code: 400}));
49
- } else if (!payload.data.payload.body) {
50
- return reject(assistant.errorify(`Missing required parameter: body`, {code: 400}));
51
- }
52
-
53
- // Fix other values
54
- payload.data.payload.author = payload.data.payload.author || 'alex';
55
- payload.data.payload.affiliate = payload.data.payload.affiliate || '';
56
- payload.data.payload.tags = payload.data.payload.tags || '';
57
- payload.data.payload.categories = payload.data.payload.categories || '';
58
-
59
- // Fix even more values
60
- payload.data.payload.layout = payload.data.payload.layout || 'app/blog/post';
61
- payload.data.payload.date = payload.data.payload.date || moment(now).format('YYYY-MM-DD');
62
- payload.data.payload.id = payload.data.payload.id || Math.round(new Date(now).getTime() / 1000);
63
- payload.data.payload.path = payload.data.payload.path || `./_posts/${moment(now).format('YYYY')}/guest`;
64
- payload.data.payload.githubUser = payload.data.payload.githubUser || bemRepo.user;
65
- payload.data.payload.githubRepo = payload.data.payload.githubRepo || bemRepo.name;
66
-
67
- // Log
68
- assistant.log(`main(): Creating post...`, payload.data.payload);
69
-
70
- // Extract all images
71
- await self.extractImages();
72
-
73
- // Set defaults
74
- const finalContent = powertools.template(POST_TEMPLATE, payload.data.payload);
75
-
76
- // Log
77
- assistant.log(`main(): finalContent`, finalContent);
78
-
79
- });
80
-
81
- };
82
-
83
- // Extract images
84
- Module.prototype.extractImages = function () {
85
- const self = this;
86
- const Manager = self.Manager;
87
- const Api = self.Api;
88
- const assistant = self.assistant;
89
- const payload = self.payload;
90
-
91
- return new Promise(async function(resolve, reject) {
92
- // Extract images
93
- const matches = payload.data.payload.body.matchAll(IMAGE_REGEX);
94
- const images = Array.from(matches).map(match => ({
95
- src: match[2] || '',
96
- alt: match[1] || uuidv4(),
97
- }));
98
-
99
- // Log
100
- assistant.log(`extractImages(): images`, images);
101
-
102
- if (!images) {
103
- return resolve();
104
- }
105
-
106
- // Loop through images
107
- for (let index = 0; index < images.length; index++) {
108
- const image = images[index];
109
-
110
- // download image
111
- const download = await self.downloadImage(image.src, image.alt);
112
-
113
- // Log
114
- assistant.log(`extractImages(): download`, download);
115
-
116
- // const src = image.src;
117
- // const alt = image.alt;
118
- // const hyphenated = hyphenate(alt);
119
- // const tag = powertools.template(IMAGE_TAG, {name: hyphenate(alt), alt: alt});
120
-
121
- // console.log(`Image ${index + 1}: src=${image.src}, alt='${image.alt}'`);
122
- }
123
-
124
- // const image = images[i];
125
- // const src = image.match(LINK_REGEX)[2];
126
- // const alt = image.match(LINK_REGEX)[1] || uuidv4();
127
- // const name = hyphenate(alt);
128
- // const tag = IMAGE_TAG.replace('{name}', name).replace('{alt}', alt);
129
-
130
- // // Log
131
- // assistant.log(`extractImages(): original`, image.match(LINK_REGEX));
132
- // assistant.log(`extractImages(): image`, image);
133
- // assistant.log(`extractImages(): src`, src);
134
- // assistant.log(`extractImages(): alt`, alt);
135
- // assistant.log(`extractImages(): name`, name);
136
- // assistant.log(`extractImages(): tag`, tag);
137
-
138
- // // Replace image
139
- // payload.data.payload.body = payload.data.payload.body
140
- // .replace(image, tag);
141
-
142
- // // Save image
143
- // await self.saveImage(src, id, name);
144
-
145
- // Resolve
146
- return resolve();
147
- });
148
- };
149
-
150
- // Downlaod image
151
- Module.prototype.downloadImage = function (src, alt) {
152
- const self = this;
153
- const Manager = self.Manager;
154
- const Api = self.Api;
155
- const assistant = self.assistant;
156
- const payload = self.payload;
157
-
158
- return new Promise(async function(resolve, reject) {
159
- // Log
160
- const hyphenated = hyphenate(alt);
161
-
162
- assistant.log(`downloadImage(): src=${src}, alt=${alt}, hyphenated=${hyphenated}`);
163
-
164
- // Get image
165
- const image = await fetch(src, {
166
- method: 'GET',
167
- download: hyphenated,
168
- })
169
- .then((r) => {
170
- // Log
171
- assistant.log(`downloadImage(): Result`, r);
172
-
173
- // Save image
174
- return resolve(r);
175
- })
176
- .catch((e) => reject(e));
177
- });
178
- };
179
-
180
- function hyphenate(s) {
181
- return s
182
- // Remove everything that is not a letter or a number
183
- .replace(/[^a-zA-Z0-9]/g, '-')
184
- // Replace multiple hyphens with a single hyphen
185
- .replace(/-+/g, '-')
186
- // Remove leading and trailing hyphens
187
- .replace(/^-|-$/g, '')
188
- // Lowercase
189
- .toLowerCase();
190
- }
191
-
192
- module.exports = Module;