hexo-matery添加友链朋友圈


这是冰老师做的友链朋友圈项目,我最开始看到它的时候,真就眼前一亮,这就是我想要的效果,立马加了群去了解这个项目,在部署项目的时候冰老师也一直耐心指导,给冰老师点赞👍冰老师还直接帮忙写了css,我觉得这个项目真的特别不错,就转载了冰老师的文章,另外帮他宣传宣传这个项目(虽然我这是个时常日ip不到20的贵站,但是说不定有人看到了呢)。

先上图
matery效果图
我的朋友圈页面

开始正文

首先上冰老师的教程
一直做到第三步,第四步你可以跳回这里。

添加页面

1.在hexo根目录下source文件夹下添加文件夹friendcircle并新建index.md文件写入以下内容

---
title: 朋友圈
date: 2021-02-19 19:27:32
type: "friendcircle"
layout: "friendcircle"
---

2.下载 js 文件并配置前往冰老师提供的地址hexo-moments-js下载 js。修改 js 中的 api 地址(冰老师教程第三步的api链接),
将以下代码中的api链接换位你部署的 api 链接即可。

requests_url = "https://hexo-circle-of-friends-api.vercel.app/api";

改完以后将该js文件保存在主题目录下source/js文件夹下
3.在主题目录下layout文件夹中新建friendcircle.ejs写入以下内容

<%- partial('_partial/bg-cover') %>
<style>
  .journal {
    padding: 12px;
    border: 1px dashed #e6e6e6;
    color: #969696;
    position: relative;
    display: inline-block;
    width: 95%;
    background: #fbfbfb50;
    border-radius: 10px;
    font-size: 16px;
    margin: 12px auto;
  }
</style>
<main class="content">
  <div class="container">
    <div class="card">
      <div class="card-content">
        <div class="journal">
          <div class="title center-align">“友链朋友圈”</div>
        </div>
        <div id="friend_link_circle">
          <h2>统计信息</h2>

          <div id="info_user_poor" class="article-sort-item" style="display: flex; box-shadow: rgba(0, 0, 0, 0.07) 0px 2px 2px 0px, rgba(0, 0, 0, 0.1) 0px 1px 5px 0px; border-radius: 2px">
            <div class="chart">
              <span class="friend_post_info_title">当前友链数:</span><span class="friend_post_info_number">&#123;&#123;user_lenth&#125;&#125;个</span><br />
              <span class="friend_post_info_title">失败数:</span><span class="friend_post_info_number">&#123;&#123;error&#125;&#125;个</span><br />
            </div>
            <div class="chart">
              <span class="friend_post_info_title">活跃友链数:</span><span class="friend_post_info_number">&#123;&#123;unique_live_link&#125;&#125;个</span><br />
              <span class="friend_post_info_title">当前库存:</span><span class="friend_post_info_number">&#123;&#123;listlenth&#125;&#125;篇</span><br />
            </div>
            <div class="chart">
              <span class="friend_post_info_title">今日更新:</span><span class="friend_post_info_number">&#123;&#123;today_post&#125;&#125;篇</span><br />
              <span class="friend_post_info_title">最近更新:</span><span class="friend_post_info_number">&#123;&#123;last_update_time&#125;&#125;</span>
            </div>
          </div>
          <div v-for="datalist in datalist_slice">
            <h2 v-if="datalist[2]-maxnumber<0">&#123;&#123;datalist[0]&#125;&#125;</h2>
            <div v-if="item[6]-maxnumber<0" v-for="(item,i) in datalist[1]" class="article-sort-item" style="box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.07), 0 1px 5px 0 rgba(0, 0, 0, 0.1); border-radius: 2px">
              <a :target="opentype" class="article-sort-item-img" :href="item[2]" :title="item[0]"><img onerror='this.onerror=null,this.src="/medias/pyq/404.png"' data-ll-status="loaded" class="entered loaded" :src="item[4]" /></a>
              <div class="article-sort-item-info">
                <div class="article-sort-item-time">
                  <i class="far fa-user"></i>
                  <span style="padding-left: 10px; padding-right: 10px">&#123;&#123;item[3]&#125;&#125;</span>
                  <div class="friend_post_time">
                    <i class="far fa-calendar-alt"></i>
                    <time class="post-meta-date-created" :datetime="item[1]" :title="item[1]">&#123;&#123;item[1]&#125;&#125;</time>
                  </div>
                </div>
                <a :target="opentype" style="-webkit-line-clamp: 1" class="article-sort-item-title" :href="item[2]" :title="item[0]">&#123;&#123;item[0]&#125;&#125;</a>
              </div>
            </div>
          </div>
          <div style="text-align: center">
            <button v-if="loadmore_display" type="button" class="load_button" @click="addmaxnumber()">加载更多...</button>
          </div>
        </div>

        <style>
          .friend_post_info_title {
            font-weight: 700;
          }
          .friend_post_info_number {
            float: right;
          }

          .chart {
            align-items: flex-start;
            flex: 1;
            width: 100px;
            height: 60px;
            margin: 20px;
          }

          @media screen and (max-width: 500px) {
            #info_user_poor {
              padding: 10px;
              flex-direction: column;
              max-height: 200px;
            }
            .chart {
              flex: 0;
              width: 100%;
              height: 160px;
              margin: 0;
            }
          }
          .article-sort-item:before {
            border: none;
          }
          @media screen and (min-width: 500px) {
            .friend_post_time {
              float: right;
            }
          }
          .load_button {
            -webkit-transition-duration: 0.4s; /* Safari */
            transition-duration: 0.4s;
            text-align: center;
            border: 1px solid #ededed;
            border-radius: 0.3em;
            display: inline-block;
            background: transparent;
            color: #555;
            padding: 0.5em 1.25em;
          }

          .load_button:hover {
            color: #3090e4;
            border-color: #3090e4;
          }
        </style>
        <style>
          .friend_post_info_title {
            font-weight: 700;
          }
          .friend_post_info_number {
            float: right;
          }

          .chart {
            align-items: flex-start;
            flex: 1;
            width: 100px;
            height: 60px;
            margin: 20px;
          }

          @media screen and (max-width: 500px) {
            #info_user_poor {
              padding: 10px;
              flex-direction: column;
              max-height: 200px;
            }
            .chart {
              flex: 0;
              width: 100%;
              height: 160px;
              margin: 0;
            }
          }
          .article-sort-item:before {
            border: none;
          }
          @media screen and (min-width: 500px) {
            .friend_post_time {
              float: right;
            }
          }
          .load_button {
            -webkit-transition-duration: 0.4s; /* Safari */
            transition-duration: 0.4s;
            text-align: center;
            border: 1px solid #ededed;
            border-radius: 0.3em;
            display: inline-block;
            background: transparent;
            color: #555;
            padding: 0.5em 1.25em;
          }

          .load_button:hover {
            color: #3090e4;
            border-color: #3090e4;
          }
          .article-sort-item {
            position: relative;
            display: -webkit-box;
            display: -moz-box;
            display: -webkit-flex;
            display: -ms-flexbox;
            display: box;
            display: flex;
            -webkit-box-align: center;
            -moz-box-align: center;
            -o-box-align: center;
            -ms-flex-align: center;
            -webkit-align-items: center;
            align-items: center;
            margin: 0 0 1rem 0.5rem;
            -webkit-transition: all 0.2s ease-in-out;
            -moz-transition: all 0.2s ease-in-out;
            -o-transition: all 0.2s ease-in-out;
            -ms-transition: all 0.2s ease-in-out;
            transition: all 0.2s ease-in-out;
          }
          .article-sort-item-img {
            overflow: hidden;
            width: 80px;
            height: 80px;
          }
          .article-sort-item-img img {
            max-width: 100%;
          }
          .article-sort-item-info {
            -webkit-box-flex: 1;
            -moz-box-flex: 1;
            -o-box-flex: 1;
            box-flex: 1;
            -webkit-flex: 1;
            -ms-flex: 1;
            flex: 1;
            padding: 0 0.8rem;
          }
          .article-sort-item-title {
            display: -webkit-box;
            overflow: hidden;
            -webkit-box-orient: vertical;
            font-size: 1.1em;
            -webkit-transition: all 0.3s;
            -moz-transition: all 0.3s;
            -o-transition: all 0.3s;
            -ms-transition: all 0.3s;
            transition: all 0.3s;
            -webkit-line-clamp: 1;
          }
        </style>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
        <script src="/js/request.js"></script>
      </div>
    </div>
  </div>
</main>

4.在主题yml中添加到导航栏

朋友圈:
    url: /pyq
    icon: fa fa-puzzle-piece

5.添加404图片
如果友链头像错误,直接显示404图片下载该图片并在主题目录下medias文件夹下新建文件夹pyq保存(你也可以自己更改ejs里的位置随意保存)。

最后hexo三连即可,再次感谢冰老师。

本地化api

由于近来vercel十分不稳定动不动就被q,所以我就把友链朋友圈的api放到了本地,如果你也需要的话请先完成集成化部署(源码再github私人仓库),可以参考前文配置。另外需要保证你的友链朋友圈的action还在正常运行,友链朋友圈的action作用是把友链信息友链文章爬取下来保存在leancloud里。友链朋友圈api项目是把leancloud里存放的数据写成json,以便调用。所以我就把冰老师友链朋友圈api项目拿了过来小小的修改一下。为了避免跨域问题需要将原来request.js移到与友链朋友圈同一目录下。如下结构图所示:

source 
└── friendcircle
    └── index.md //辅助统计页面渲染
    └── request.js //处理数据
    └── requirements.txt
    └── api
        └──index.py //获取保存数据

.github
    └── workflows
        └── api.yml //定时启动api.py更新数据

将request.js覆盖写入如下内容

requests_url = 'api/api.json?date';
const friend_link_circle = new Vue({
    el: '#friend_link_circle',
    data: {
        datalist: [],
        datalist_slice:[],
        maxnumber:20,
        addnumber:10,
        display:true,
        loadmore_display:false,
        listlenth:0,
        today_post:0,
        last_update_time:'',
        user_lenth:'',
        error:0,
        unique_live_link:0,
        opentype:'_blank'  //'_blank'打开新标签,'_self'本窗口打开

    },
    methods:{
        unique (arr) {
            return Array.from(new Set(arr))
        },
        formatDate(strDate) {
            try{
                let date = new Date(Date.parse(strDate.replace(/-/g, "/")));
                let gettimeoffset = 0
                if (new Date().getTimezoneOffset()){
                    gettimeoffset = new Date().getTimezoneOffset();
                }
                else{
                    gettimeoffset = 8;
                }
                let timeoffset = gettimeoffset * 60 * 1000;
                let len = date.getTime();
                let date2 = new Date(len - timeoffset);
                let sec = date2.getSeconds().toString();
                let min =  date2.getMinutes().toString();
                if (sec.length === 1) {
                    sec = "0" + sec;
                }
                if (min.length === 1) {
                    min = "0" + min;
                }
                return date2.getFullYear().toString() + "/" + (date2.getMonth() + 1).toString() + "/" + date2.getDate().toString() + " " + date2.getHours().toString() + ":" + min + ":" + sec
            }catch(e){return ""}
        },
        timezoon(){
            let time = this.datalist_slice[0][1][0][5];
            return this.formatDate(time)
        },
        todaypost(){
            let date= new Date();
            let year = date.getFullYear();
            let month =(date.getMonth() + 1).toString();
            let day = (date.getDate()).toString();
            if (month.length === 1) {
                month = "0" + month;
            }
            if (day.length === 1) {
                day = "0" + day;
            }
            return  year + "-" + month + "-" + day
        },
        addmaxnumber(){
            this.maxnumber = this.maxnumber + this.addnumber;
            if (this.maxnumber >= this.listlenth){
                this.loadmore_display=false;
            }
        },
        slice(data){
            let monthlist=[];
            let datalist=[];
            let data_slice = data;
            for (let item in data_slice) {
                data_slice[item].push(item);
                if (data_slice[item][1].lenth !== 10) {
                    let list = data_slice[item][1].split('-')
                        if (list[1].length < 2){
                            list[1]="0" + list[1]
                        }
                        if (list[2].length < 2){
                            list[2]="0" + list[2]
                        }
                    data_slice[item][1] = list.join('-')
                    }
                let month=data_slice[item][1].slice(0,7);
                if(monthlist.indexOf(month) !== -1){
                    console.log(month);
                    datalist[monthlist.length-1][1].push(data_slice[item]);
                }
                else{
                    monthlist.push(month);
                    datalist.push([month,[data_slice[item]]]);
                }
            }

            for (let mounthgroup  of datalist){
                mounthgroup.push(mounthgroup[1][0][6]);
            }
            console.log(datalist);
            return datalist
        }
    },
    mounted: function () {

        fetch(requests_url+new Date()).then(
            data => data.json()
    ).then(
            data => {
                let today = this.todaypost();
                let Datetody = new Date(today);
                for (let item = 0; item <data[1].length ;item++){
                    let Datedate = new Date(data[1][item][1])
                    if (Datedate>Datetody){
                        data[1].splice(item --, 1);
                        console.log('穿越了');
                    }
                }
                this.datalist = data[1];
                this.listlenth = data[1].length;
                this.user_lenth = data[0].length;
                this.datalist_slice = this.slice(data[1]);
                this.last_update_time =this.timezoon();
                this.loadmore_display = true;
                let link_list=[];
                for (let item of data[1]){
                    if (item[1] === today ){
                        this.today_post +=1;
                    }
                    link_list.push(item[3]);
                }
                let arr = this.unique(link_list);
                this.unique_live_link = arr.length;
                for (let item of data[0]){
                    if (item[3] === 'true' ){
                        this.error +=1;
                    }
                }
            }
        )

    }
})

主要是修改了api请求地址以及在加上了时间戳减少浏览器缓存的影响。

如上新建好index.py后在里面写入如下内容(注意修改里面的appid和AppKey为你友链朋友圈的leancloud的appid和AppKey):

# -*- codeing = utf-8 -*-
# @Time : 2021/02/08 9:05 上午
# @Author : Zfour
# @File : spyder1.py
# @Software : PyCharm

import leancloud
from http.server import BaseHTTPRequestHandler
import json
import datetime

def getdata():
    list = ['title','time','link','author','headimg']
    list_user = ['frindname','friendlink','firendimg','error']
    # Verify key
    leancloud.init('appid', 'AppKey')

    # Declare class
    Friendspoor = leancloud.Object.extend('friend_poor')

    # Create an alias for the query
    query = Friendspoor.query

    # Select the sort methods
    query.descending('time')

    # Limit the number of queries
    query.limit(1000)

    # Choose class
    query.select('title','time','link','author','headimg','createdAt')

    # Execute the query, returning result
    query_list = query.find()

    Friendlist = leancloud.Object.extend('friend_list')
    query_userinfo = Friendlist.query
    query_userinfo.limit(1000)
    query_userinfo.select('frindname','friendlink','firendimg','error')
    query_list_user = query_userinfo.find()


    # Result to arr
    datalist=[]
    for i in query_list:
        itemlist=[]
        for item in list:
            itemlist.append(i.get(item))
        update_time = i.get('createdAt')
        itemlist.append(update_time.strftime('%Y-%m-%d %H:%M:%S'))
        datalist.append(itemlist)


    datalist_user =[]
    for j in  query_list_user:
        itemlist_user=[]
        for item2 in list_user:

            itemlist_user.append(j.get(item2))
        datalist_user.append(itemlist_user)
    total_data = []
    total_data.append(datalist_user)
    total_data.append(datalist)
    return total_data
    # Api handler
class handler(BaseHTTPRequestHandler):
    def do_GET(self):
        data = getdata()
        self.send_response(200)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps(data).encode('utf-8'))
        return
print(getdata())
with open("api.json", "w") as f:
    json.dump(getdata(), f)

如上新建好requirements.txt后写入如下内容:

arrow==0.17.0
certifi==2020.12.5
cffi==1.14.4
chardet==3.0.4
gevent==1.5.0
greenlet==1.0.0
idna==2.8
iso8601==0.1.13
leancloud==2.9.0
pycparser==2.20
python-dateutil==2.8.1
qiniu==7.2.2
requests==2.22.0
secure-cookie==0.1.0
six==1.15.0
urllib3==1.25.3
Werkzeug==1.0.1

如上新建好api.yml后写入如下内容:

# 当有改动推送到master分支时,启动Action
name: 获取友链api

on:
  push:
    branches:
      - main #2020年10月后github新建仓库默认分支改为main,注意更改
  release:
    types:
      - published
  schedule:
    - cron: '    0 * * * *'#每小时执行一次
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: 检查分支
        uses: actions/checkout@v2
        with:
          ref: main #2020年10月后github新建仓库默认分支改为main,注意更改
      - name: Set up Python #安装python
        uses: actions/setup-python@v1
        with:
          python-version: 3.8
      - name: 获取数据
        run: |
          cd /home/runner/work/hexo/hexo/source/friendcircle
          pip3 install -r requirements.txt
          cd /home/runner/work/hexo/hexo/source/friendcircle/api
          python index.py 
          git config --global user.name "你的GitHub用户名如我的brqs"
          git config --global user.email "你的GitHub邮箱如3447851674@qq.com"
git add .
git commit -m "友链朋友圈api更新"
git push origin main

完成之后上传github即可自动运行。

思考

其实这样做也存在一些弊端,解决vercel不稳定问题的最简单的办法还是用couldflare反代一层最为简单快速。


文章作者: 不染轻裳
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 不染轻裳 !
评论
 上一篇

阅读全文

mc服务器进入3.0时代
mc服务器进入3.0时代 mc服务器进入3.0时代
由于突发意外(应该是积患已久,如果不咕咕咕的话应该会写一下前因后果),mc的服务器崩了,但好在mc服务端数据都还在,俗话说的好只要数据在一切都可以重来,还变得更好,之前升配云服务器就完全用来玩mc了,用我好兄弟的账号又买了一台学生机把网站
2021-03-09
下一篇 

阅读全文

三人一狗牛山红色之旅
三人一狗牛山红色之旅 三人一狗牛山红色之旅
为什么要写这篇文章呢?目的是为了记录下这次略显惨痛的爬山之旅,今天距离那天已经过去两天了,我的屁股和小腿还酸疼,一次性运动量有点大。也为同我们一起上山的狗而写。对于它来说,我们只是它生命中的过客,而它却陪我们上山下山最后目送我们远远离开才
2021-03-04
  目录