This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021.

preface

Do you want to learn about github Action? Do you want to know how to send email notifications? Do you want to have the ability to post nuggets regularly? Let’s see how to do that!

It only takes 10 minutes to read this article, and you can reap the rewards

  1. How to use Github Action to perform scheduled tasks

  2. Understand the detailed implementation of gold mining article timing release

  3. How do I use scripts to send email notifications

We all had good intentions once upon a time

1.1 You and I have experienced the scene!

Late at night, are you still writing, reluctant to fall asleep for a long time?

Three rounds already passed, good article finally become, release button gently a bit, “wake up tomorrow thousand praise will belong to me”, hey hey 😋, I think fondly.

After a day, I only get a few page views and a few upvotes for the good in-depth article I worked so hard for last night. It’s so sad. Dear digg friends, why don’t you give me upvotes for such a good article?

Is it your bad writing? Shit, no, it’s the wrong time.

1.2 Why so few page views?

  1. Late night post: although we are very able to stay up, but so late I’m afraid only a few people really see.

  2. Work time post: we all like to touch fish, but after all, there are some “furtive”, read your article naturally a lot less people.

  3. Saturday, Sunday or holidays: the rare time to rest and play, we all have girlfriends to accompany it!

1.3 How to catch the morning traffic peak?

I don’t know if you have noticed that the data of articles published between 8 and 10 in the morning is generally better, probably because the majority of digger friends are on their commute at this time, you love learning, and are always brushing the nuggets.

With more exposure opportunities and good content quality, it meets the reading and learning needs of a certain group of people. I believe that the reading volume and praise will not be bad.

1.4 What if I can’t get up so early?

What’s the point of talking so much? The point is I can’t get up! Working so late yesterday how did you get up today The Nuggets don’t have a timing feature.

And last (not least)

The above is just a personal guess, but also a sidelong way, we do not believe it, attention should still be on the content and quality of the article.

2. Sneak peek and regular release

The following is a test done at a temporary time when writing an article. A total of three sets of templates are designed, which can sense the success, failure and login state failure of the article.

2.1 Publishing the Template Successfully

You can see the title of the article, introduction, cover, click directly into the details of the article

2.2 Failed to Publish a Template

You can view error information for troubleshooting

2.3 Logging In to an Invalid Template

Notification after login failure

3. Easy to use in just three steps

If you also want to use the timed publishing function, follow me to perform these three steps, and you will have your own customization soon!

3.1 Git Clone source code

The source address

git clone https://github.com/qianlongo/juejin-auto-publish-article-template.git

Copy the code

3.2 Simple Configuration

Locate the Config configuration file, and you’re almost done with some simple configuration

// Configuration information
module.exports = {
  headers: {
  // Nuggets browser search cookie sessionID posted here
    cookie: 'sessionid=xxxx',},email: {
    // Usually fill in your own QQ mailbox
    sendUserEmail: '[email protected]'.// Sender's mailbox
    / / get process here at https://www.jeecms.com/xdjq/864.htm
    sendUserPass: 'xxxx'.// The password of the sender's mailbox
    // Usually fill in your own QQ mailbox
    toUserEmail: '[email protected]'.// To whom, fill in the mailbox
  },
  // Your gold digger user information
  userInfo: {
    / / avatar
    avatar: 'xxx'./ / nickname
    nickName: 'xxx',}}Copy the code

3.3 [AR] Annotate the article

For example, I am writing this article at night, just add [AR] at the beginning of the article and it will be published at 8 o ‘clock tomorrow morning

[AR] Nuggets "regularly publish articles"Here you are, trying to create.Copy the code

4. Implementation process

4.1 General Ideas

The main problems to be solved are timing, how to publish gold digging articles, and how to inform the results

Timing: We all know that github Action can help us to execute scripts at the specified time (there will be certain colors), so we can directly go to the whoring (or you can buy a server, set up a scheduled task)!

How to publish gold digging articles: Simulate the process of publishing articles manually, analyzing requests and simulating sending requests.

Nodemailer is very convenient for sending emails. We can receive the results by email (generally wechat will bind QQ email notification).

4.2 Request Analysis

The common headers is used for each request as follows

headers: {
  origin: 'https://juejin.cn'.referer: 'https://juejin.cn/'.'user-agent': 'the Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36'.'content-type': 'application/json'.Sessionid = sessionID = sessionID
  cookie: 'sessionid=xxx',},Copy the code

4.2.1 Obtaining drafts of all articles

First of all, we publish articles in the draft box, and all subsequent operations are from this. The main parameters have been marked, and some of them can be seen directly from the name.

What if we have 10 drafts, but we only want to send one tomorrow and one the day after?

At this point we can fiddle with the title, for example:

  1. [AR] This is for tomorrow
  2. [AR 2021-11-10] This is the article to be posted at the specified time
  3. This is an article that doesn’t need to be published automatically because it hasn’t been written yet

So with the simple identity of [AR], we can easily implement the function of publishing specified articles

// Request correlation

url: https://api.juejin.cn/content_api/v1/article_draft/list_by_user
method: 'post'
data: { "keyword": ""."page_size": 10, page_no }

// Response correlation

{
  "err_no": 0."err_msg": "success"."data": [{
    / / article id
    "id": "7028201579387830308"."article_id": "0"."user_id": "3438928099549352"."category_id": "0"."tag_ids": []."link_url": ""."cover_image": "https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9daf770b07f845a7bd2d9ebcd3753b65~tplv-k3u1fbpfcp-watermark.image?"."is_gfw": 0.// Title of the article
    "title": "Gold digger" regularly publishes articles \" Here you are, just trying to create.".//
    "brief_content": "1. Do you want more traffic, more likes? 1.1 You and I have experienced the scene! 1.2 Why so few page views? Late night post: although we are very able to stay up, but so late I'm afraid only a few people really see. Posted in the afternoon: "Everyone likes it."."is_english": 0."is_original": 1."edit_type": 10."html_content": ""."mark_content": ""."ctime": "1636380800"."mtime": "1636477399"."status": 0."original_type": 0}]."count": 21
}

Copy the code

4.2 Obtaining details of the draft article

To get the details of the draft article, it is necessary to remove the [AR] flag just described

// Request correlation
url: https://api.juejin.cn/content_api/v1/article_draft/detail
method: post
// The ID of the draft article
data: { draft_id }

// Response correlation
{
  "err_no": 0."err_msg": "success"."data": {
    "draft_id": "7028201579387830308".// Just focus on this field
    "article_draft": {
        "id": "7028201579387830308"."article_id": "0"."user_id": "3438928099549352".Note that this returns an array in which each member is of numeric type, but needs to be converted to a string in an update article
        "category_id": []."tag_ids": []."link_url": ""."cover_image": "https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9daf770b07f845a7bd2d9ebcd3753b65~tplv-k3u1fbpfcp-watermark.image?"."is_gfw": 0."title": "[AR] Nuggets "regular post article \" here comes, just for your efforts to create."."brief_content": ""."is_english": 0."is_original": 1."edit_type": 10."html_content": "deprecated"."mark_content": ""."ctime": "1636380800"."mtime": "1636478151"."status": 0."original_type": 0
    },
    "author_user_info": {... },"category": {... },"tags": []."user_interact": {},"columns": []}}Copy the code

4.3 Updating articles

By retrieving draft details, we can remove [AR] from the article title and restore the original article title

// Request correlation
url: https://api.juejin.cn/content_api/v1/article_draft/update
method: post
data: {
    "id": "7028201579387830308"."category_id": "6809637767543259144".// Notice here
    "tag_ids": ["6809640407484334093"."6809640398105870343"."6809640369764958215"]."link_url": ""./ / cover
    "cover_image": ""."is_gfw": 0."title": "Gold digger" regularly publishes articles \" here you are, just trying to create. "./ / profile
    "brief_content": "xxx"."is_english": 0."is_original": 1."edit_type": 10."html_content": "deprecated"."mark_content": "xxxx"
}


// Response correlation
{
  "err_no": 0."err_msg": "success"."data": {
    "id": "7028201579387830308"."article_id": "0"."user_id": "0"."category_id": "6809637767543259144"."tag_ids": [6809640407484334093.6809640398105870343.6809640369764958215]."link_url": ""."cover_image": ""."is_gfw": 0."title": "Gold digger" regularly publishes articles \" here you are, just trying to create. "."brief_content": ""."is_english": 0."is_original": 1."edit_type": 10."html_content": "deprecated"."mark_content": ""."ctime": "62135596800"."mtime": "62135596800"."status": 0."original_type": 0}}Copy the code

4.4 Publishing Articles

Finally, the most important interface for Posting articles is very simple

// Request correlation
url: https://api.juejin.cn/content_api/v1/article/publish
method: post
data: {
  // the other two parameters are not found
  draft_id: ' '.sync_to_org: false.column_ids: []}// Response correlationWhen the page is published, it jumps awayCopy the code

4.3 Source code implementation

log.js

Log.js is mainly used to cache the information printed during the process of publishing articles, so that the results can be directly fed back to the email in case of errors, and the problem can be seen at a glance

let catchLogs = []

module.exports = {
  log (str) {
    / / cache
    catchLogs.push(str)
    console.log(str)
  },
  getLogs () {
    / / read
    return catchLogs.join('\n')
  },
  clear () {
    / / remove
    catchLogs = []
  }
}

Copy the code

sendEmail.js

Post email Notification



Copy the code

For sending mail

const nodemailer = require('nodemailer')
const emailTemplate = require('./emailTemplate')
const { email, titleMap, subTitleMap } = require('./config')
const { 
  sendUserEmail,
  sendUserPass,
  toUserEmail,
} = email

const sendMail = async (data) => {
  const title = titleMap[data.templateType]
  const subTitle = subTitleMap[data.templateType]
  const html = emailTemplate(data)
  let transporter = nodemailer.createTransport({
    host: 'smtp.qq.com'.port: '465'.secureConnection: true.auth: {
      user: sendUserEmail,
      pass: sendUserPass,
    }
  })
  const sendEmailOptions = {
    // The subject of the email
    from: `"${title}" ${sendUserEmail}`./ / sent to who
    to: toUserEmail,
    // Subtitle: subject
    subject: subTitle,
    // See emailtemplate.js below for the main content of the email
    html: html,
  }

  await transporter.sendMail(sendEmailOptions)
}

module.exports = sendMail

Copy the code

emailTemplate.js

Email template for post results. Here I’ve designed about three templates (actually only two). See the UI at the top of this post


const { userInfo } = require('./config')

module.exports = emailTemplate = (options) = > {
  const{ articleInfos = [], ... others } = options || {}const { avatar, nickName, introduce } = userInfo || {}
  const { templateType = 'success', errorInfo= ' ' } = others || {}
  const templateMap = {
    success: ` <div class="user-info" style="display: flex; align-items: center; padding: 8px 20px;" > <img style="width: 60px; height: 60px; border-radius: 50%; margin-right: 10px;" class="avatar" src="${avatar}" alt=""> <div class="user-detail"> <div class="nick-name" style="font-size: 16px; font-weight: bold; The line - height: 1.2; color: #000;" >${nickName}</div> <div class="desc" style="color: #30445a; font-size: 12px; padding-top: 6px;" >${introduce}</div>
        </div>
      </div>
      <div class="main">
        ${
          articleInfos.map((articleInfo) => {
            const { title, link, brief_content: briefContent, cover_image: coverImage } = articleInfo || []
            return `
              <div
              class="content-wrapper"
              style="
                display: flex;
                padding-bottom: 12px;
                border-bottom: 1px solid #e5e6eb;
                padding: 12px 20px;
              "
            >
              <div class="content-main" style="flex: 1 1 auto">
                <div class="title-row" style="display: flex; margin-bottom: 8px">
                  <a
                    href="${link}"
                    target="_blank"
                    rel=""
                    title="${title}" class="title" style=" font-weight: 700; font-size: 16px; line-height: 24px; color: #1d2129; width: 100%; display: -webkit-box; overflow: hidden; text-overflow: ellipsis; -webkit-box-orient: vertical; -webkit-line-clamp: 1; text-decoration: none; cursor: pointer; ">${title}</a
                  >
                </div>
                <div class="abstract" style="margin-bottom: 10px">
                  <a
                    href="${link}" target="_blank" rel="" style=" color: #86909c; font-size: 13px; line-height: 22px; display: -webkit-box; overflow: hidden; text-overflow: ellipsis; -webkit-box-orient: vertical; text-decoration: none; -webkit-line-clamp: 2; ">${briefContent}
                  </a>
                </div>
              </div>
              <div
                class="lazy thumb"
                style="
                  flex: 0 0 auto;
                  width: 120px;
                  height: 80px;
                  margin-left: 24px;
                  background-color: #e0e0e0;
                  position: relative;
                  border-radius: 6px;
                  -o-object-fit: cover;
                  object-fit: cover;
                  background-image: url(${coverImage});
                  background-size: cover;
                "
              ></div>
            </div>
            `})}
       
      </div>
    `.error: ` <div style="max-width: 375px; padding: 12px 20px; display: flex; justify-content: center; align-items: center; flex-direction: column;" > <img style="width: 80%;" SRC = "https://img13.360buyimg.com/ddimg/jfs/t1/158409/29/29190/14574/618a67a3E5125f564/b678db2f277c1d1b.jpg" Alt = "" > < div  style="color: #86909c; font-size: 13px; font-weight: 500;" > oh! < div style="width: 100%; margin-top: 10px; padding: 0; overflow: scroll; color: #86909c;" >${ errorInfo }
      </pre>
    </div>
    `.notLogin: ` 
      
< div style="color: #86909c; font-size: 13px; font-weight: 500;" > oh! Juejin cookie
,}return ` <! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> </head> <body style="margin: 0">${templateMap[ templateType ]} </body> </html> ` } Copy the code

config.js

// Configuration information
module.exports = {
  headers: {
    origin: 'https://juejin.cn'.referer: 'https://juejin.cn/'.'user-agent': 'the Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36'.'content-type': 'application/json'.Sessionid = sessionID = sessionID
    cookie: 'sessionid=ce4fd61e80752ac58d3528f380d5714f',},email: {
    sendUserEmail: '[email protected]'.// Sender's mailbox
    sendUserPass: 'cmkfvixcvrfdgahh'.// The password of the sender's mailbox
    toUserEmail: '[email protected]'.// To whom, fill in the mailbox
  },
  // success error notLogin
  // Configure it according to your preference
  // Titles in different states
  titleMap: {
    success: 'Nuggets regularly issue alerts'.error: 'Nuggets regularly issue alerts'.notLogin: 'Nuggets regularly issue alerts'
  },
  // Subheadings in different states
  subTitleMap: {
    success: 'congratulations! The article was published successfully! '.error: 'Post error! '.notLogin: 'Login failed! '
  },
  // Your gold digger user information
  userInfo: {
    / / avatar
    avatar: 'https://p3-passport.byteacctimg.com/img/user-avatar/1957416d67153775f273064ea766a8d3~300x300.image'./ / nickname
    nickName: 'Front End Bighead'./ / is introduced
    introduce: 'Today is another hard day, refueling refueling oh!! ',}}Copy the code

4.3.1 Get all draft articles and filter the articles to be published

// Recursively get all articles in the draft box
const getAllDraftArticles = async (allDraftArticles = [], page_no = 1) = > {const result = await axios({
    url: 'https://api.juejin.cn/content_api/v1/article_draft/list_by_user'.method: 'post',
    headers,
    data: { "keyword": ""."page_size": 10, page_no }
  })
  const draftArticles = result.data
  const count = result.count

  allDraftArticles = allDraftArticles.concat(draftArticles)
  
  if (allDraftArticles.length === count) {
    log(The first step: the draft box is detected${allDraftArticles.length}Article `)
    return allDraftArticles
  } else {
    return getAllDraftArticles(allDraftArticles, page_no + 1)}}// Filter upcoming posts
const PUB_ARTICLE_RE = /[\ [\ []AR(?:)?(\d{4}-\d{2}-\d{2})?[\]\]]/ I
const filterPubArticle = (articles = []) = > {
  // Notice here that github Action is set 8 hours earlier than Beijing time
  const today = dayjs(new Date()).add(8.'h').format('YYYY-MM-DD HH:mm:ss')

  log(Step â‘¡ : Start screening needs today (${today}) published articles')

  const filterResult = articles.filter((article) = > {
    const matchResult = article.title.match(PUB_ARTICLE_RE)
    const pubDate = matchResult && matchResult[1]
    // If a date is specified, compare date time, year-month-day
    if (pubDate) {
      const isTodayPub = today === pubDate

      log(` articles${article.title}, specifying a release date${pubDate}Is today,${today}.${isTodayPub ? 'Coming out today' : 'Release date not reached'}`)

      return isTodayPub
    } else {
      [AR] [AR 2011-11-10
      return matchResult
    }
  }).map((it) = > {
    return {
      ...it,
      title: it.title.replace(PUB_ARTICLE_RE, ' '),
      link: `https://juejin.cn/post/${it.id}`
    }
  })

  log(` - screen${filterResult.length}An article has been marked and will be published today ---- ')

  return filterResult
}


Copy the code

4.3.2 Obtaining details of articles in draft

// Get the details of the article in the draft
const getDraftDetail = async (draft_id) => {
  const result = await axios({
    url: 'https://api.juejin.cn/content_api/v1/article_draft/detail'.method: 'post'.data: {
      draft_id
    },
    headers
  })

  return result.data
}


Copy the code

4.3.3 Update draft articles


// Update the article
const updateDraftDetails = async (draft_articles = []) => {
  log('Step 3: Remove the timed publication identifier from the title')
  return Promise.all(draft_articles.map(async (article) => {
    const { article_draft } = await getDraftDetail(article.id)
    const originTitle = article_draft.title.replace(PUB_ARTICLE_RE, ' ')

    const result = await axios({
      url: 'https://api.juejin.cn/content_api/v1/article_draft/update'.method: 'post',
      headers,
      data: {
        ...article_draft,
        title: originTitle,
        // We need to convert the tag, because the detail tag is number
        tag_ids: article.tag_ids.map((tagId) = > ' ' + tagId)
      }
    })

    log(`${article.title}: has removed the logo, can be published in the original)

    return result
  }))
}

Copy the code

4.3.4 Publish articles

const publishArticles = (articles = []) = > {
  log('Step 4: Start Posting articles one by one')
  return Promise.all(articles.map(async (article) => {
    log('Publish articles:${article.title} ${article.link}`)
    const result = await axios({
      url: 'https://api.juejin.cn/content_api/v1/article/publish'.method: 'post',
      headers,
      data: {
        draft_id: article.id,
        sync_to_org: false.column_ids: []
      }
    })

    log('Published successfully')

    return result
  })) 
}

Copy the code

4.3.5 Integral entrance

const init = async() = > {try {
    // 1. Get all the articles in the draft box
    const draftArticles = await getAllDraftArticles()
    // 2. Filter the articles to be published today
    const filterPubArticles = filterPubArticle(draftArticles)
    
    if (filterPubArticles.length) {
      // 3. Update the title of the screening results and remove [AR 2021-11-08] and [AR] labels
      await updateDraftDetails(filterPubArticles)
      // 4. Start Posting articles
      await publishArticles(filterPubArticles)
      // log(filterPubArticleResult)

      sendEmail({
        templateType: 'success'.articleInfos: filterPubArticles
      })
    }
    
  } catch (err) {
    console.log('Wrong', err)
    try {
      log(JSON.stringify(err))
    } catch (e) {
      console.log(e)
    }
    
    sendEmail({
      templateType: 'error'.errorInfo: getLogs(),
    })
    clear()
  }  
}

init()
Copy the code

5 Make an appointment Goodbye

It’s late at night. I wish you all good night