开发vscode插件「markdown文章一键发布」之CSDN实现篇

哈喽,大家好!我是「励志前端小黑哥」,我带着最新发布的文章又来了!

老规矩,小手动起来~点赞关注不迷路!

上篇文章,咱们对插件的技术方案进行了选型,项目终于可以投入正式开发了。

这篇文章,我们就来实现一下CSDN文章一键发布的核心逻辑。

喜报!!!目前插件的预览版本已经做好了,大家想提前体验的朋友,文末附下载链接,这里补充一个使用视频:

视频-插件使用效果

本人前几篇文章就已经使用上了,效果杠杠的!

需求回顾

此插件的大致需求是:一键发布vscode保存的markdown文件到CSDN、知乎、掘进等三方平台。

插件的详细需求分析,请参考我的另一篇文章《基于vscode的markdown文章一键发布客户端 - 需求梳理》

话不多说,开干!

界面流程分析

因为技术选型是puppeteer,实质上就是一个浏览器,平常你怎么用的,你就调用它怎么操作。

所以首先,我们需要分析一下,要在CSDN发布一篇文章需要经过的步骤,这里就用图例的形式展示给大家吧!

第一步,打开浏览器,跳转到https://csdn.net,这一步是浏览器的初始化阶段

第二步,扫码登录:
文章配图

第三步,点击发文按钮:
文章配图

第四步,输入标题、输入内容、点击保存草稿:
文章配图

简单的几个步骤,一篇CSDN文章就保存好了,这几步非常关键,后面都会用到。

有了这几步,咱么就可以对应的写一下入口函数了!

代码实现

入口函数设计

这里就直接贴上我写的代码吧!

/**
 * CSDN发布文章的入口函数
 */
 export async function startSync() {
     // 第一步,初始化
     await pageInit();
     // 第二步,登录并跳转至编辑器
     await loginAndRedirect();

    // 获取vscode编辑器的markdown文本,并完成转换
    const mdTextToEditor = await contentCreator();
    // 第三步,输入标题、输入内容
    await inputResult(mdTextToEditor)
    // 第四步,保存
    const url = await saveArticle();
    // 第五步,结束
    await pageExit();
}

可以看到,代码里的步骤,与我们上一小节文章内的步骤,基本是一模一样的,每一步都对应一个处理函数。

通过一步步的拆分,咱们把整个流程分解为了一个个小小的函数。现目前咱不用管函数内的具体实现,把基础框架先搭建出来,下面我就带大家慢慢的来实现这些函数。

Tips:这一步在软件开发过程中叫接口设计,其实不止是后端开发才有接口设计,前端开发过程中也是需要的!

页面初始化函数的实现 pageInit

首先第一个要实现的函数是pageInit,函数的功能和相关知识点,是我们需要提前知道的。

  • 函数的主要功能:浏览器初始化、page初始化。
  • 相关的知识点:puppeteer浏览器操作page操作
    这些知识点大家就自己去看看了哈,其实就是一些API文档,如果有需要,后续我再专门来讲这些!

此处提醒一下新手的开发朋友,API文档只是工具,有需要的时候拿出来查一查就行了!没必要死记硬背的。

下面就贴一个大致实现代码吧,如下:

import * as puppeteer from "puppeteer";

// page全局访问
const page:puppeteer.Page;

/**
 * browser初始化、page初始化
 */
async function pageInit() {
    // 打开浏览器
    const browser = await puppeteer.launch({
        // 本地浏览器的路径,我这里是MacOS的地址
        executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
        args: [
            '--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled', '--enable-automation=false'
        ],
        headless: true,
        channel: 'chrome',
        defaultViewport: {
            width: 1200,
            height: 768
        }
    });
    // 新开页面
    page = await browser.newPage();
    // 跳页至https://csdn.net/,直到无新的网络加载才返回
    await page.goto(
         'https://csdn.net/',
         { waitUntil: ['domcontentloaded', 'load', 'networkidle0'] }
    );
}

登录函数的实现 loginAndRedirect

同理,我们需要知道登录函数的功能以及相关的知识点:

  • 函数的主要功能:扫码登录CSDN、跳转至CSDN的编辑器界面
  • 相关的知识点:puppeteerpage操作
    大致如下:
/**
 * 校验登录、打开csdn的md编辑器
 */
async function loginAndRedirect() {
    // 校验登录
    const { success, imgurl } = await checkLogin(page);
    // 未登录时提示登录
    if (!success) return '请扫码登录!';    
    // 跳页
    await page.goto(
        'https://editor.csdn.net/md/',
        { waitUntil: ['domcontentloaded', 'load', 'networkidle0'] }
    );
}

/**
 * 校验登录,方法:同时寻找登录的扫码图片和头像,谁先找到就用谁返回
 * 若未登录,还将返回扫码二维码url
 * @param page 
 * @returns {Promise<{success: boolean;imgurl: string;}>}
 */
async function checkLogin(page: Page): Promise<{ success: boolean; imgurl: string; }> {
    // 登录二维码
    const loginTask = page.waitForSelector('.login-box .login-code-wechat .public-code');
    // 头像任务
    const avatarTask = page.waitForSelector('#csdn-toolbar .toolbar-inside.exp3 .toolbar-btn.toolbar-btn-login-new a.hasAvatar', { timeout: 120000 });
    // 登录结果
    const result = {
        // 成功 or 失败
        success: false,
        // 扫码登录的二维码地址
        imgurl: ''
    }
    return new Promise(resolve => {
        loginTask.then(async loginElement => {
            const imgurl = (await page.$eval('.login-box .login-code-wechat .public-code>img', (ele) => (ele as HTMLImageElement).src))!;
            result.success = false;
            result.imgurl = imgurl;
            resolve(result);
        }).catch(e => {
            resolve(result)
        })
        avatarTask.then(ele => {
            result.success = true;
            resolve(result)
        }).catch(e => {
            resolve(result)
        })
    })
}

上面的代码,最关键的部分要数checkLogin这个子方法了。它的功能是:同时寻找未登录时的二维码已登录时的头像,判断谁先找到,从而判断是否已登录CSDN。这个设计是不是非常巧妙呢?

同理,我们再继续实现剩下来的几个函数接口吧

输入内容函数的实现 inputResult

  • 函数的主要功能:输入标题、输入内容
  • 相关的知识点:puppeteerpage操作vscode插件的document操作
/**
 * 将最终结果输入到编辑器、输入框
 * @param mdTextToEditor 
 */
 async function inputResult(mdTextToEditor: string) {
    // 输入内容
    const editor = await page.$('.editor__inner')
    await editor?.type(mdTextToEditor);
    // 输入标题,标题取markdown的文件名
    const input = await page.$('.article-bar__input-box .article-bar__title--input')
    await input?.type(vscode.window.activeTextEditor.document.fileName.split('/').pop() + '');
}

文章保存函数的实现 saveArticle

  • 函数的主要功能:点击保存按钮、判断成功
  • 相关的知识点:puppeteerpage操作
/**
 * 保存文章草稿
 * @param page 
 */
  async function saveArticle() {
      const submitBtn = await page.$('.article-bar__user-box>button.btn-save');
      submitBtn?.click()
      // 点击保存后,有一次重定向,监听此次重定向来判断是否成功
      await page.waitForNavigation();
      const url = await page.url();
      console.log('草稿保存成功', url)
    return url
  }

界面退出函数的实现 pageExit

  • 函数的主要功能:关闭浏览器、关闭界面
  • 相关的知识点:puppeteerpage操作
async function pageExit() {
    // 关闭界面
    await page.close()
    // 关闭浏览器
    await page.browser().close();
}

至此,整个流程的核心逻辑就实现完了!可以看到,所需的知识点都是puppeteer,只要掌握了它,其实是很简单的!

问题与优化

核心代码倒是很简单,不过本人在开发过程中也做了很多的优化。这些优化的代码并不是本文的重点,这里就不一一贴出来了,仅罗列在此:

  • puppeteer如何防止csdn的自动化检测
  • puppeteer如何存储上一次的登录状态
  • puppeteer如何设置UserAgent
  • puppeteer如何设置代理,并输入秘钥
  • puppeteer如何防止界面弹窗
  • puppeteer如何上传图片
  • markdown文档如何处理

这些优化问题,如果你也遇到了,欢迎留言或者给我发私信,很乐意给你解答!

插件预览版安装

现目前的开发进度,已支持CSDN平台的一键发布,欢迎大家下载使用!

链接:https://pan.baidu.com/s/1zlF-2YnJEJKVW6lpt_COwg

提取码:5khx

链接7天有效,过期了可以私聊我哦~

结语

这篇文章,介绍了CSDN的登录发文步骤,使用接口的形式对每个步骤进行了细分,还采用puppeteer实现了插件的核心逻辑。开发过程中遇到的优化点,欢迎大家留言私信!

预告

下篇文章,咱们就来看看这个核心逻辑怎么嵌入到VSCODE,从而形成一个完整可运行的插件,敬请期待!

励志前端小黑哥,全网唯一账号!
关注我,带你了解更多前端知识!

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

励志前端小黑哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值