Skip to content

Commit

Permalink
feat: v4.23.0
Browse files Browse the repository at this point in the history
  • Loading branch information
surmon-china committed Sep 19, 2023
1 parent e600761 commit 1f91bc1
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 139 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "surmon.me",
"version": "4.22.0",
"version": "4.23.0",
"description": "Surmon.me blog",
"author": "Surmon",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion src/app/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export const routes: RouteRecordRaw[] = [
meta: {
responsive: false,
layout: LayoutColumn.Full,
ssrCacheTTL: 60 * 60 * 1 // 1 hours
ssrCacheTTL: false
}
},
{
Expand Down
16 changes: 2 additions & 14 deletions src/bff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,8 @@ createExpressApp().then(async ({ app, server, cache }) => {
// sitemap
app.get('/sitemap.xml', async (_, response) => {
try {
const data = await cacher({
cache,
key: 'sitemap',
ttl: 60 * 60 * 1, // 1 hours
getter: getSitemapXml
})
response.header('Content-Type', 'application/xml')
response.send(data)
response.send(await getSitemapXml())
} catch (error) {
errorer(response, { message: error })
}
Expand All @@ -64,14 +58,8 @@ createExpressApp().then(async ({ app, server, cache }) => {
// RSS
app.get('/rss.xml', async (_, response) => {
try {
const data = await cacher({
cache,
key: 'rss',
ttl: 60 * 60 * 1, // 1 hours
getter: getRssXml
})
response.header('Content-Type', 'application/xml')
response.send(data)
response.send(await getRssXml())
} catch (error) {
errorer(response, { message: error })
}
Expand Down
6 changes: 3 additions & 3 deletions src/pages/about/statistic/github.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@
<p>
<i class="iconfont icon-star-outline"></i>
<span v-if="isZhLang">共获得</span>
<statistic-count large primary split :count="store.data?.statistics.stars" />
<statistic-count large primary split :count="store.data?.totalStarCount" />
<span v-if="isZhLang">个 star</span>
<span v-else>stars earned</span>
</p>
<p>
<i class="iconfont icon-repository"></i>
<span v-if="isZhLang">共维护</span>
<statistic-count :count="store.data?.repositories.length" />
<statistic-count :count="store.data?.repositoryCount" />
<span v-if="isZhLang">个开源项目</span>
<span v-else>open-source repos</span>
</p>
<p>
<i class="iconfont icon-organization"></i>
<span v-if="isZhLang">维护/发起</span>
<statistic-count :count="store.data?.organizations.length" />
<statistic-count :count="store.data?.organizationCount" />
<span v-if="isZhLang">个开源组织</span>
<span v-else>organizations</span>
</p>
Expand Down
6 changes: 3 additions & 3 deletions src/pages/about/statistic/npm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@
<p class="line-1">
<i class="iconfont icon-package"></i>
<span v-if="isZhLang">发布了</span>
<statistic-count :count="store.totalPackages" />
<statistic-count :count="store.data?.totalPackages" />
<span v-if="isZhLang">个公共软件包</span>
<span v-else>packages</span>
</p>
<p>
<i class="iconfont icon-download"></i>
<span v-if="isZhLang">被下载</span>
<statistic-count large primary split :count="store.totalDownloads" />
<statistic-count large primary split :count="store.data?.totalDownloads" />
<span v-if="isZhLang">次</span>
<span v-else>downs</span>
</p>
<p>
<i class="iconfont icon-score"></i>
<span v-if="isZhLang">平均评分</span>
<statistic-count :count="store.averageScore" />
<statistic-count :count="store.data?.averageScore" />
<span v-if="isZhLang">分</span>
<span v-else>average score</span>
</p>
Expand Down
1 change: 0 additions & 1 deletion src/pages/archive/mobile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
const statisticState = useArchivePageStatistics()
const statisticFetching = ref(true)
const statistics = computed(() => [
statisticState.statistics.value.todayViews,
statisticState.statistics.value.articles,
statisticState.statistics.value.comments,
statisticState.statistics.value.totalLikes
Expand Down
60 changes: 56 additions & 4 deletions src/server/getters/open-srouce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,63 @@
import axios from 'axios'
import { IDENTITIES } from '@/config/app.config'

const fetchStatisticJSON = async (fileName: string) => {
const fetchStatisticJSON = async <T = any>(fileName: string): Promise<T> => {
const url = `https://raw.githubusercontent.com/${IDENTITIES.GITHUB_USER_NAME}/${IDENTITIES.GITHUB_USER_NAME}/release/${fileName}`
const response = await axios.get<string>(url, { timeout: 6000 })
const response = await axios.get<T>(url, { timeout: 6000 })
return response.data
}

export const getGitHubStatistic = () => fetchStatisticJSON('github.json')
export const getNPMStatistic = () => fetchStatisticJSON('npm.json')
export interface GitHubStatistic {
followerCount: number
followingCount: number
gistCount: number
repositoryCount: number
organizationCount: number
totalStarCount: number
totalCodeSize: number
}

export const getGitHubStatistic = () => {
return fetchStatisticJSON<any>('github.json').then((data) => ({
followerCount: data.userinfo.followers,
followingCount: data.userinfo.following,
gistCount: data.userinfo.public_gists,
repositoryCount: data.userinfo.public_repos,
organizationCount: data.organizations.length,
totalStarCount: data.statistics.stars,
totalCodeSize: data.statistics.size
}))
}

export interface NpmStatistic {
totalPackages: number
totalDownloads: number
averageScore: number
}

interface NPM_JSON {
downloads: Record<string, number>
packages: Array<any>
}

export const getNPMStatistic = () => {
return fetchStatisticJSON<NPM_JSON>('npm.json').then((data) => {
const totalPackages = data ? Object.keys(data.downloads).length : 0
const totalDownloads = data ? Object.values<number>(data.downloads).reduce((p, c) => p + c, 0) : 0
const averageScore = (() => {
const packages = data?.packages
if (!packages?.length) {
return 0
}
// https://itnext.io/increasing-an-npm-packages-search-score-fb557f859300
const totalScore = packages.reduce((p, c) => p + c.score.final, 0)
return (totalScore / packages.length).toFixed(3)
})()

return {
totalPackages,
totalDownloads,
averageScore
} as NpmStatistic
})
}
112 changes: 38 additions & 74 deletions src/server/getters/twitter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import { IDENTITIES } from '@/config/app.config'
import { getSotweAggregate, improveSotweTweet, SotweTweet } from './sotwe'
import { getNitterRss } from './nitter'

export interface TwitterUserinfo {
name: string
Expand Down Expand Up @@ -42,25 +41,19 @@ export interface TwitterAggregate {
}

export const getTwitterAggregate = async (): Promise<TwitterAggregate> => {
// Use of different public services to ensure high availability
const [nitter, sotwe] = await Promise.all([
getNitterRss(IDENTITIES.TWITTER_USER_NAME).catch((error) => {
console.warn('[Twitter] nitter rss is empty.', error?.message ?? String(error))
return null
}),
getSotweAggregate(IDENTITIES.TWITTER_USER_NAME).catch((error) => {
console.warn('[Twitter] sotwe aggregate is empty.', error?.message ?? String(error))
return null
})
])
if (!nitter && !sotwe) {
const sotwe = await getSotweAggregate(IDENTITIES.TWITTER_USER_NAME).catch((error) => {
console.warn('[Twitter] sotwe aggregate is empty.', error?.message ?? String(error))
return null
})

if (!sotwe) {
return Promise.reject('[Twitter] aggregate data is empty.')
}

const tweets: Array<TwitterTweet> = []
const userinfo: TwitterUserinfo = {
name: sotwe?.info?.name || nitter?.userinfo.name || IDENTITIES.TWITTER_USER_NAME,
avatar: sotwe?.info?.profileImageOriginal || nitter?.userinfo.avatar || '',
name: sotwe?.info?.name || IDENTITIES.TWITTER_USER_NAME,
avatar: sotwe?.info?.profileImageOriginal || '',
description: sotwe?.info.description || void 0,
location: sotwe?.info.location || void 0,
tweetCount: sotwe?.info.postCount ?? void 0,
Expand All @@ -79,67 +72,38 @@ export const getTwitterAggregate = async (): Promise<TwitterAggregate> => {
})
}

// 1. nitter & sotwe > mix
// 2. nitter only
if (nitter?.tweets.length) {
nitter.tweets.forEach((tweet) => {
// No longer reserved for reprinting retweet, as it causes data to be inconsistent in both the model and the front-end
if (tweet.isRetweet) {
return
}
// 3. sotwe only
sotweTweets.forEach((tweet) => {
// not retweet
if (tweet.retweetedStatus) {
return
}
// not reply to other
if (tweet.inReplyToUserId && tweet.inReplyToUserId !== sotwe.info.id) {
return
}
// not your own tweet
if (tweet.user && tweet.user.id !== sotwe.info.id) {
return
}

const found = sotweTweets.find((item) => item.id === tweet.id)
tweets.push({
id: tweet.id,
owner: tweet.owner,
text: found ? improveSotweTweet(found, false) : tweet.text,
html: found ? improveSotweTweet(found, true) : tweet.html,
date: found?.createdAt || tweet.date,
location: found?.location?.name || void 0,
favoriteCount: found?.favoriteCount ?? void 0,
retweetCount: found?.retweetCount ?? void 0,
mediaCount: found?.mediaEntities?.length ?? tweet.mediaCount ?? 0,
isReply: (found?.inReplyToUserId ? true : tweet.isReply) ?? void 0,
isQuote: found ? !!found?.quotedStatus : void 0,
isRetweet: (found?.retweetedStatus ? true : tweet.isRetweet) ?? void 0,
hasVideo: found?.mediaEntities?.some((media) => media.type === 'video'),
hasImage: found?.mediaEntities?.some((media) => media.type === 'photo') || tweet.hasImage
})
tweets.push({
id: tweet.id,
owner: IDENTITIES.TWITTER_USER_NAME,
text: improveSotweTweet(tweet, false),
html: improveSotweTweet(tweet, true),
date: tweet.createdAt,
location: tweet.location?.name || void 0,
favoriteCount: tweet.favoriteCount ?? void 0,
retweetCount: tweet.retweetCount ?? void 0,
mediaCount: tweet.mediaEntities?.length ?? 0,
isReply: !!tweet.inReplyToUserId,
isQuote: !!tweet.quotedStatus,
isRetweet: !!tweet.retweetedStatus,
hasVideo: tweet.mediaEntities?.some((media) => media.type === 'video'),
hasImage: tweet.mediaEntities?.some((media) => media.type === 'photo')
})
// 3. sotwe only
} else if (sotwe && sotweTweets.length) {
sotweTweets.forEach((tweet) => {
// not retweet
if (tweet.retweetedStatus) {
return
}
// not reply to other
if (tweet.inReplyToUserId && tweet.inReplyToUserId !== sotwe.info.id) {
return
}
// not your own tweet
if (tweet.user && tweet.user.id !== sotwe.info.id) {
return
}

tweets.push({
id: tweet.id,
owner: IDENTITIES.TWITTER_USER_NAME,
text: improveSotweTweet(tweet, false),
html: improveSotweTweet(tweet, true),
date: tweet.createdAt,
location: tweet.location?.name || void 0,
favoriteCount: tweet.favoriteCount ?? void 0,
retweetCount: tweet.retweetCount ?? void 0,
mediaCount: tweet.mediaEntities?.length ?? 0,
isReply: !!tweet.inReplyToUserId,
isQuote: !!tweet.quotedStatus,
isRetweet: !!tweet.retweetedStatus,
hasVideo: tweet.mediaEntities?.some((media) => media.type === 'video'),
hasImage: tweet.mediaEntities?.some((media) => media.type === 'photo')
})
})
}
})

return { userinfo, tweets }
}
8 changes: 5 additions & 3 deletions src/server/getters/twitter/nitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { XMLParser } from 'fast-xml-parser'

// https://github.com/zedeus/nitter
// https://github.com/zedeus/nitter/wiki/Instances
// https://nitter.net/${username}
// https://twiiit.com/${username}/rss
// https://singapore.unofficialbird.com/${username}/rss
// ✅ https://nitter.net/${username}
// ❌ https://nitter.net/${username}/rss
// ❌ https://twiiit.com/${username}/rss
// ❌ https://singapore.unofficialbird.com/${username}
// ❌ https://singapore.unofficialbird.com/${username}/rss

export interface NitterRss {
userinfo: {
Expand Down
38 changes: 3 additions & 35 deletions src/stores/statistic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
* @author Surmon <https://github.com/surmon-china>
*/

import { computed } from 'vue'
import { defineStore } from 'pinia'
import { useFetchStore } from './_fetch'
import { TunnelModule } from '/@/constants/tunnel'
import type { GitHubStatistic, NpmStatistic } from '/@/server/getters/open-srouce'
import nodepress from '/@/services/nodepress'
import tunnel from '/@/services/tunnel'

Expand All @@ -32,49 +32,17 @@ export const useNodepressStatisticStore = defineStore('nodepressStatistic', () =
})

export const useGitHubStatisticStore = defineStore('githubStatistic', () => {
return useFetchStore<any>({
return useFetchStore<GitHubStatistic | null>({
once: true,
data: null,
fetcher: () => tunnel.dispatch(TunnelModule.OpenSourceGitHubStatistic)
})
})

export interface NpmStatistic {
downloads: Record<string, number>
packages: Array<any>
}

export const useNpmStatisticStore = defineStore('npmStatistic', () => {
const fetchStore = useFetchStore<NpmStatistic | null>({
return useFetchStore<NpmStatistic | null>({
once: true,
data: null,
fetcher: () => tunnel.dispatch<NpmStatistic>(TunnelModule.OpenSourceNPMStatistic)
})

const totalPackages = computed(() => {
return fetchStore.data.value ? Object.keys(fetchStore.data.value.downloads).length : 0
})

const totalDownloads = computed(() => {
return fetchStore.data.value
? Object.values<number>(fetchStore.data.value.downloads).reduce((p, c) => p + c, 0)
: 0
})

const averageScore = computed(() => {
const packages = fetchStore.data.value?.packages
if (!packages?.length) {
return 0
}
// https://itnext.io/increasing-an-npm-packages-search-score-fb557f859300
const totalScore = packages.reduce((p, c) => p + c.score.final, 0)
return (totalScore / packages.length).toFixed(3)
})

return {
...fetchStore,
totalPackages,
totalDownloads,
averageScore
}
})

0 comments on commit 1f91bc1

Please sign in to comment.