kopia lustrzana https://github.com/Stopka/fedicrawl
Properly applied code quality tools
rodzic
405cf2f181
commit
29acce3906
|
@ -12,9 +12,9 @@ ENV ELASTIC_URL='http://elastic:9200' \
|
||||||
FROM prebuild AS build
|
FROM prebuild AS build
|
||||||
WORKDIR /srv
|
WORKDIR /srv
|
||||||
COPY application/package*.json ./
|
COPY application/package*.json ./
|
||||||
RUN npm install
|
RUN yarn
|
||||||
COPY application/. .
|
COPY application/. .
|
||||||
RUN npm run build
|
RUN yarn build
|
||||||
|
|
||||||
FROM build AS dev
|
FROM build AS dev
|
||||||
CMD npx tsc --watch
|
CMD npx tsc --watch
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2021": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:react/recommended",
|
|
||||||
"standard"
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"jsx": true
|
|
||||||
},
|
|
||||||
"ecmaVersion": 12,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
"react",
|
|
||||||
"@typescript-eslint"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"printWidth": 100,
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
Plik diff jest za duży
Load Diff
|
@ -8,46 +8,43 @@
|
||||||
"url": "skorpil.cz",
|
"url": "skorpil.cz",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npx tsc --watch",
|
"dev": "tsc --watch",
|
||||||
"clean": "npx rimraf dist",
|
"clean": "rimraf dist",
|
||||||
"build": "npx tsc",
|
"build": "tsc",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
||||||
"start": "node dist/app",
|
"start": "node dist/app",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,test}/**/*.{ts,js}\" --fix",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
"start:deploy": "npm run start"
|
"start:deploy": "yarn start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elastic/elasticsearch": "^8.2.1",
|
"@elastic/elasticsearch": "^8.4.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.27.2",
|
||||||
"geoip-lite": "^1.4.6",
|
"geoip-lite": "^1.4.6",
|
||||||
"npmlog": "^6.0.0",
|
"npmlog": "^6.0.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"striptags": "^3.2.0",
|
"striptags": "^3.2.0",
|
||||||
"typescript-collections": "^1.3.3",
|
"typescript-collections": "^1.3.3",
|
||||||
"zod": "^3.11.6"
|
"zod": "^3.19.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/geoip-lite": "^1.4.1",
|
"@types/geoip-lite": "^1.4.1",
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "^29.0.3",
|
||||||
"@types/node": "^18.7.18",
|
"@types/node": "^18.7.18",
|
||||||
"@types/npmlog": "^4.1.3",
|
"@types/npmlog": "^4.1.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
"@typescript-eslint/parser": "^5.4.0",
|
"@typescript-eslint/parser": "^5.0.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^8.23.1",
|
||||||
"eslint-config-standard": "^16.0.3",
|
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "^2.25.2",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-n": "^15.0.0",
|
||||||
"eslint-plugin-promise": "^5.1.1",
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
"eslint-plugin-react": "^7.27.1",
|
"jest": "^29.0.3",
|
||||||
"jest": "^27.3.0",
|
"ts-jest": "^29.0.1",
|
||||||
"standard": "*",
|
"typescript": "^4.3.0"
|
||||||
"ts-jest": "^27.0.7",
|
|
||||||
"typescript": "^4.3.5"
|
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
|
@ -62,5 +59,23 @@
|
||||||
},
|
},
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
}
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"env": {
|
||||||
|
"es2021": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"standard-with-typescript"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"project": [
|
||||||
|
"tsconfig.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslintIgnore": [
|
||||||
|
"dist",
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"prettier": "prettier-config-standard"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export class NoSupportedLinkError extends Error {
|
export class NoSupportedLinkError extends Error {
|
||||||
public constructor (domain:string) {
|
public constructor (domain: string) {
|
||||||
super(`No supported link node info link for ${domain}`)
|
super(`No supported link node info link for ${domain}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,13 @@ import { retrieveWellKnown } from './retrieveWellKnown'
|
||||||
import { retrieveNodeInfo, NodeInfo } from './retrieveNodeInfo'
|
import { retrieveNodeInfo, NodeInfo } from './retrieveNodeInfo'
|
||||||
import { NoSupportedLinkError } from './NoSupportedLinkError'
|
import { NoSupportedLinkError } from './NoSupportedLinkError'
|
||||||
|
|
||||||
export const retrieveDomainNodeInfo = async (domain:string):Promise<NodeInfo> => {
|
export const retrieveDomainNodeInfo = async (
|
||||||
|
domain: string
|
||||||
|
): Promise<NodeInfo> => {
|
||||||
const wellKnown = await retrieveWellKnown(domain)
|
const wellKnown = await retrieveWellKnown(domain)
|
||||||
const link = wellKnown.links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0')
|
const link = wellKnown.links.find(
|
||||||
|
(link) => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0'
|
||||||
|
)
|
||||||
if (typeof link === 'undefined') {
|
if (typeof link === 'undefined') {
|
||||||
throw new NoSupportedLinkError(domain)
|
throw new NoSupportedLinkError(domain)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,24 +9,26 @@ const schema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
version: z.string()
|
version: z.string()
|
||||||
}),
|
}),
|
||||||
protocols: z.array(
|
protocols: z.array(z.string()),
|
||||||
z.string()
|
usage: z.optional(
|
||||||
|
z.object({
|
||||||
|
users: z.optional(
|
||||||
|
z.object({
|
||||||
|
total: z.optional(z.number()),
|
||||||
|
activeMonth: z.optional(z.number()),
|
||||||
|
activeHalfyear: z.optional(z.number())
|
||||||
|
})
|
||||||
|
),
|
||||||
|
localPosts: z.optional(z.number())
|
||||||
|
})
|
||||||
),
|
),
|
||||||
usage: z.optional(z.object({
|
|
||||||
users: z.optional(z.object({
|
|
||||||
total: z.optional(z.number()),
|
|
||||||
activeMonth: z.optional(z.number()),
|
|
||||||
activeHalfyear: z.optional(z.number())
|
|
||||||
})),
|
|
||||||
localPosts: z.optional(z.number())
|
|
||||||
})),
|
|
||||||
openRegistrations: z.optional(z.boolean())
|
openRegistrations: z.optional(z.boolean())
|
||||||
})
|
})
|
||||||
|
|
||||||
export type NodeInfo = z.infer<typeof schema>
|
export type NodeInfo = z.infer<typeof schema>
|
||||||
|
|
||||||
export const retrieveNodeInfo = async (url: string): Promise<NodeInfo> => {
|
export const retrieveNodeInfo = async (url: string): Promise<NodeInfo> => {
|
||||||
console.info('Retrieving node info', { url: url })
|
console.info('Retrieving node info', { url })
|
||||||
const nodeInfoResponse = await axios.get(url, {
|
const nodeInfoResponse = await axios.get(url, {
|
||||||
timeout: getDefaultTimeoutMilliseconds()
|
timeout: getDefaultTimeoutMilliseconds()
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,7 +15,7 @@ const wellKnownSchema = z.object({
|
||||||
export type WellKnown = z.infer<typeof wellKnownSchema>
|
export type WellKnown = z.infer<typeof wellKnownSchema>
|
||||||
|
|
||||||
export const retrieveWellKnown = async (domain: string): Promise<WellKnown> => {
|
export const retrieveWellKnown = async (domain: string): Promise<WellKnown> => {
|
||||||
console.info('Retrieving well known', { domain: domain })
|
console.info('Retrieving well known', { domain })
|
||||||
const wellKnownUrl = `https://${domain}/.well-known/nodeinfo`
|
const wellKnownUrl = `https://${domain}/.well-known/nodeinfo`
|
||||||
const wellKnownResponse = await axios.get(wellKnownUrl, {
|
const wellKnownResponse = await axios.get(wellKnownUrl, {
|
||||||
timeout: getDefaultTimeoutMilliseconds(),
|
timeout: getDefaultTimeoutMilliseconds(),
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import { FieldData } from './FieldData'
|
import { FieldData } from './FieldData'
|
||||||
|
|
||||||
export interface FeedData {
|
export interface FeedData {
|
||||||
name:string,
|
name: string
|
||||||
displayName:string,
|
displayName: string
|
||||||
description:string,
|
description: string
|
||||||
followersCount: number,
|
followersCount: number
|
||||||
followingCount:number,
|
followingCount: number
|
||||||
statusesCount?:number,
|
statusesCount?: number
|
||||||
bot?:boolean,
|
bot?: boolean
|
||||||
url: string,
|
url: string
|
||||||
avatar?:string,
|
avatar?: string
|
||||||
locked:boolean,
|
locked: boolean
|
||||||
lastStatusAt?:Date,
|
lastStatusAt?: Date
|
||||||
createdAt:Date
|
createdAt: Date
|
||||||
fields: FieldData[],
|
fields: FieldData[]
|
||||||
type: 'account'|'channel'
|
type: 'account' | 'channel'
|
||||||
parentFeed?: {
|
parentFeed?: {
|
||||||
name:string
|
name: string
|
||||||
hostDomain:string
|
hostDomain: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { FeedProviderMethod } from './FeedProviderMethod'
|
import { FeedProviderMethod } from './FeedProviderMethod'
|
||||||
|
|
||||||
export interface FeedProvider {
|
export interface FeedProvider {
|
||||||
getKey: ()=>string
|
getKey: () => string
|
||||||
retrieveFeeds: FeedProviderMethod
|
retrieveFeeds: FeedProviderMethod
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
import { FeedData } from './FeedData'
|
import { FeedData } from './FeedData'
|
||||||
|
|
||||||
export type FeedProviderMethod = (domain: string, page: number) => Promise<FeedData[]>
|
export type FeedProviderMethod = (
|
||||||
|
domain: string,
|
||||||
|
page: number
|
||||||
|
) => Promise<FeedData[]>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export interface FieldData{
|
export interface FieldData {
|
||||||
name: string,
|
name: string
|
||||||
value: string,
|
value: string
|
||||||
verifiedAt: Date|undefined
|
verifiedAt: Date | undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,18 @@ import { FeedProvider } from '../FeedProvider'
|
||||||
|
|
||||||
const MastodonProvider: Provider = {
|
const MastodonProvider: Provider = {
|
||||||
getKey: () => 'mastodon',
|
getKey: () => 'mastodon',
|
||||||
getNodeProviders: ():NodeProvider[] => [{
|
getNodeProviders: (): NodeProvider[] => [
|
||||||
getKey: () => 'peers',
|
{
|
||||||
retrieveNodes: retrievePeers
|
getKey: () => 'peers',
|
||||||
}],
|
retrieveNodes: retrievePeers
|
||||||
getFeedProviders: ():FeedProvider[] => [{
|
}
|
||||||
getKey: () => 'users',
|
],
|
||||||
retrieveFeeds: retrieveLocalPublicUsersPage
|
getFeedProviders: (): FeedProvider[] => [
|
||||||
}]
|
{
|
||||||
|
getKey: () => 'users',
|
||||||
|
retrieveFeeds: retrieveLocalPublicUsersPage
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MastodonProvider
|
export default MastodonProvider
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { z } from 'zod'
|
||||||
import { getDefaultTimeoutMilliseconds } from '../../getDefaultTimeoutMilliseconds'
|
import { getDefaultTimeoutMilliseconds } from '../../getDefaultTimeoutMilliseconds'
|
||||||
import { FeedProviderMethod } from '../FeedProviderMethod'
|
import { FeedProviderMethod } from '../FeedProviderMethod'
|
||||||
import { NoMoreFeedsError } from '../NoMoreFeedsError'
|
import { NoMoreFeedsError } from '../NoMoreFeedsError'
|
||||||
|
import { FeedData } from '../FeedData'
|
||||||
|
|
||||||
const limit = 500
|
const limit = 500
|
||||||
|
|
||||||
|
@ -41,19 +42,22 @@ const schema = z.array(
|
||||||
type Emoji = z.infer<typeof emojiSchema>
|
type Emoji = z.infer<typeof emojiSchema>
|
||||||
|
|
||||||
const replaceEmojis = (text: string, emojis: Emoji[]): string => {
|
const replaceEmojis = (text: string, emojis: Emoji[]): string => {
|
||||||
emojis.forEach(emoji => {
|
emojis.forEach((emoji) => {
|
||||||
text = text.replace(
|
text = text.replace(
|
||||||
RegExp(`:${emoji.shortcode}:`, 'gi'),
|
RegExp(`:${emoji.shortcode}:`, 'gi'),
|
||||||
`<img draggable="false" class="emoji" title="${emoji.shortcode}" alt="${emoji.shortcode}" src="${emoji.url}" />`
|
`<img draggable="false" class="emoji" title="${emoji.shortcode}" alt="${emoji.shortcode}" src="${emoji.url}" />`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
export const retrieveLocalPublicUsersPage: FeedProviderMethod = async (domain, page) => {
|
export const retrieveLocalPublicUsersPage: FeedProviderMethod = async (
|
||||||
|
domain,
|
||||||
|
page
|
||||||
|
): Promise<FeedData[]> => {
|
||||||
const response = await axios.get('https://' + domain + '/api/v1/directory', {
|
const response = await axios.get('https://' + domain + '/api/v1/directory', {
|
||||||
params: {
|
params: {
|
||||||
limit: limit,
|
limit,
|
||||||
offset: page * limit,
|
offset: page * limit,
|
||||||
local: true
|
local: true
|
||||||
},
|
},
|
||||||
|
@ -64,31 +68,33 @@ export const retrieveLocalPublicUsersPage: FeedProviderMethod = async (domain, p
|
||||||
if (responseData.length === 0) {
|
if (responseData.length === 0) {
|
||||||
throw new NoMoreFeedsError('user')
|
throw new NoMoreFeedsError('user')
|
||||||
}
|
}
|
||||||
return responseData.map(
|
return responseData.map((item) => {
|
||||||
item => {
|
return {
|
||||||
return {
|
name: item.username,
|
||||||
name: item.username,
|
displayName: replaceEmojis(item.display_name, item.emojis),
|
||||||
displayName: replaceEmojis(item.display_name, item.emojis),
|
description: replaceEmojis(item.note, item.emojis),
|
||||||
description: replaceEmojis(item.note, item.emojis),
|
followersCount: item.followers_count,
|
||||||
followersCount: item.followers_count,
|
followingCount: item.following_count,
|
||||||
followingCount: item.following_count,
|
statusesCount: item.statuses_count,
|
||||||
statusesCount: item.statuses_count,
|
bot: item.bot,
|
||||||
bot: item.bot,
|
url: item.url,
|
||||||
url: item.url,
|
avatar: item.avatar,
|
||||||
avatar: item.avatar,
|
locked: item.locked,
|
||||||
locked: item.locked,
|
lastStatusAt:
|
||||||
lastStatusAt: item.last_status_at !== null ? new Date(item.last_status_at) : null,
|
item.last_status_at !== null
|
||||||
createdAt: new Date(item.created_at),
|
? new Date(item.last_status_at)
|
||||||
fields: item.fields.map(field => {
|
: undefined,
|
||||||
return {
|
createdAt: new Date(item.created_at),
|
||||||
name: replaceEmojis(field.name, item.emojis),
|
fields: item.fields.map((field) => {
|
||||||
value: replaceEmojis(field.value, item.emojis),
|
return {
|
||||||
verifiedAt: field.verified_at !== null ? new Date(field.verified_at) : null
|
name: replaceEmojis(field.name, item.emojis),
|
||||||
}
|
value: replaceEmojis(field.value, item.emojis),
|
||||||
}),
|
verifiedAt:
|
||||||
type: 'account',
|
field.verified_at !== null ? new Date(field.verified_at) : undefined
|
||||||
parentFeed: null
|
}
|
||||||
}
|
}),
|
||||||
|
type: 'account',
|
||||||
|
parentFeed: undefined
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,17 +5,18 @@ import { getDefaultTimeoutMilliseconds } from '../../getDefaultTimeoutMillisecon
|
||||||
import { NodeProviderMethod } from '../NodeProviderMethod'
|
import { NodeProviderMethod } from '../NodeProviderMethod'
|
||||||
import { NoMoreNodesError } from '../NoMoreNodesError'
|
import { NoMoreNodesError } from '../NoMoreNodesError'
|
||||||
|
|
||||||
const schema = z.array(
|
const schema = z.array(z.string())
|
||||||
z.string()
|
|
||||||
)
|
|
||||||
|
|
||||||
export const retrievePeers:NodeProviderMethod = async (domain, page) => {
|
export const retrievePeers: NodeProviderMethod = async (domain, page) => {
|
||||||
if (page !== 0) {
|
if (page !== 0) {
|
||||||
throw new NoMoreNodesError('peer')
|
throw new NoMoreNodesError('peer')
|
||||||
}
|
}
|
||||||
const response = await axios.get('https://' + domain + '/api/v1/instance/peers', {
|
const response = await axios.get(
|
||||||
timeout: getDefaultTimeoutMilliseconds()
|
'https://' + domain + '/api/v1/instance/peers',
|
||||||
})
|
{
|
||||||
|
timeout: getDefaultTimeoutMilliseconds()
|
||||||
|
}
|
||||||
|
)
|
||||||
assertSuccessJsonResponse(response)
|
assertSuccessJsonResponse(response)
|
||||||
return schema.parse(response.data)
|
return schema.parse(response.data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,18 @@ import { retrieveUsersPage } from './retrieveUsersPage'
|
||||||
|
|
||||||
const MisskeyProvider: Provider = {
|
const MisskeyProvider: Provider = {
|
||||||
getKey: () => 'misskey',
|
getKey: () => 'misskey',
|
||||||
getNodeProviders: ():NodeProvider[] => [{
|
getNodeProviders: (): NodeProvider[] => [
|
||||||
getKey: () => 'federation-instances',
|
{
|
||||||
retrieveNodes: retrieveInstancesPage
|
getKey: () => 'federation-instances',
|
||||||
}],
|
retrieveNodes: retrieveInstancesPage
|
||||||
getFeedProviders: ():FeedProvider[] => [{
|
}
|
||||||
getKey: () => 'users',
|
],
|
||||||
retrieveFeeds: retrieveUsersPage
|
getFeedProviders: (): FeedProvider[] => [
|
||||||
}]
|
{
|
||||||
|
getKey: () => 'users',
|
||||||
|
retrieveFeeds: retrieveUsersPage
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MisskeyProvider
|
export default MisskeyProvider
|
||||||
|
|
|
@ -13,29 +13,34 @@ const schema = z.array(
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
export const retrieveInstancesPage:NodeProviderMethod = async (domain, page) => {
|
export const retrieveInstancesPage: NodeProviderMethod = async (
|
||||||
const response = await axios.post('https://' + domain + '/api/federation/instances', {
|
domain,
|
||||||
host: null,
|
page
|
||||||
blocked: null,
|
) => {
|
||||||
notResponding: null,
|
const response = await axios.post(
|
||||||
suspended: null,
|
'https://' + domain + '/api/federation/instances',
|
||||||
federating: null,
|
{
|
||||||
subscribing: null,
|
host: null,
|
||||||
publishing: null,
|
blocked: null,
|
||||||
limit: limit,
|
notResponding: null,
|
||||||
offset: page * limit,
|
suspended: null,
|
||||||
sort: '+id'
|
federating: null,
|
||||||
}, {
|
subscribing: null,
|
||||||
timeout: getDefaultTimeoutMilliseconds()
|
publishing: null,
|
||||||
})
|
limit,
|
||||||
|
offset: page * limit,
|
||||||
|
sort: '+id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: getDefaultTimeoutMilliseconds()
|
||||||
|
}
|
||||||
|
)
|
||||||
assertSuccessJsonResponse(response)
|
assertSuccessJsonResponse(response)
|
||||||
const responseData = schema.parse(response.data)
|
const responseData = schema.parse(response.data)
|
||||||
if (responseData.length === 0) {
|
if (responseData.length === 0) {
|
||||||
throw new NoMoreNodesError('instance')
|
throw new NoMoreNodesError('instance')
|
||||||
}
|
}
|
||||||
return responseData.map(
|
return responseData.map((item) => {
|
||||||
item => {
|
return item.host
|
||||||
return item.host
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { z } from 'zod'
|
||||||
import { getDefaultTimeoutMilliseconds } from '../../getDefaultTimeoutMilliseconds'
|
import { getDefaultTimeoutMilliseconds } from '../../getDefaultTimeoutMilliseconds'
|
||||||
import { NoMoreFeedsError } from '../NoMoreFeedsError'
|
import { NoMoreFeedsError } from '../NoMoreFeedsError'
|
||||||
import { FeedProviderMethod } from '../FeedProviderMethod'
|
import { FeedProviderMethod } from '../FeedProviderMethod'
|
||||||
|
import { FeedData } from '../FeedData'
|
||||||
|
import { FieldData } from '../FieldData'
|
||||||
|
|
||||||
const limit = 100
|
const limit = 100
|
||||||
|
|
||||||
|
@ -42,72 +44,84 @@ const schema = z.array(
|
||||||
type Emoji = z.infer<typeof emojiSchema>
|
type Emoji = z.infer<typeof emojiSchema>
|
||||||
|
|
||||||
const replaceEmojis = (text: string, emojis: Emoji[]): string => {
|
const replaceEmojis = (text: string, emojis: Emoji[]): string => {
|
||||||
emojis.forEach(emoji => {
|
emojis.forEach((emoji) => {
|
||||||
text = text.replace(
|
text = text.replace(
|
||||||
RegExp(`:${emoji.name}:`, 'gi'),
|
RegExp(`:${emoji.name}:`, 'gi'),
|
||||||
`<img draggable="false" class="emoji" title="${emoji.name}" alt="${emoji.name}" src="${emoji.url}" />`
|
`<img draggable="false" class="emoji" title="${emoji.name}" alt="${emoji.name}" src="${emoji.url}" />`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseDescription = (description:string|null):string => {
|
const parseDescription = (description: string | null): string => {
|
||||||
if (typeof description !== 'string') {
|
if (typeof description !== 'string') {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return description.split('\n\n').map(paragraph => {
|
return description
|
||||||
paragraph = paragraph.replace('\n', '</br>\n')
|
.split('\n\n')
|
||||||
return `<p>${paragraph}</p>`
|
.map((paragraph) => {
|
||||||
}).join('\n')
|
paragraph = paragraph.replace('\n', '</br>\n')
|
||||||
|
return `<p>${paragraph}</p>`
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const retrieveUsersPage:FeedProviderMethod = async (domain, page) => {
|
export const retrieveUsersPage: FeedProviderMethod = async (
|
||||||
const response = await axios.post('https://' + domain + '/api/users', {
|
domain,
|
||||||
state: 'all',
|
page
|
||||||
origin: 'local',
|
): Promise<FeedData[]> => {
|
||||||
sort: '+createdAt',
|
const response = await axios.post(
|
||||||
limit: limit,
|
'https://' + domain + '/api/users',
|
||||||
offset: limit * page
|
{
|
||||||
}, {
|
state: 'all',
|
||||||
timeout: getDefaultTimeoutMilliseconds()
|
origin: 'local',
|
||||||
})
|
sort: '+createdAt',
|
||||||
|
limit,
|
||||||
|
offset: limit * page
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: getDefaultTimeoutMilliseconds()
|
||||||
|
}
|
||||||
|
)
|
||||||
assertSuccessJsonResponse(response)
|
assertSuccessJsonResponse(response)
|
||||||
const responseData = schema.parse(response.data)
|
const responseData = schema.parse(response.data)
|
||||||
if (responseData.length === 0) {
|
if (responseData.length === 0) {
|
||||||
throw new NoMoreFeedsError('user')
|
throw new NoMoreFeedsError('user')
|
||||||
}
|
}
|
||||||
return responseData.map(
|
return responseData.map((item) => {
|
||||||
item => {
|
return {
|
||||||
return {
|
name: item.username,
|
||||||
name: item.username,
|
displayName: replaceEmojis(item.name ?? item.username, item.emojis),
|
||||||
displayName: replaceEmojis(item.name ?? item.username, item.emojis),
|
description: replaceEmojis(
|
||||||
description: replaceEmojis(parseDescription(item.description ?? ''), item.emojis),
|
parseDescription(item.description ?? ''),
|
||||||
followersCount: item.followersCount,
|
item.emojis
|
||||||
followingCount: item.followingCount,
|
),
|
||||||
statusesCount: item.notesCount,
|
followersCount: item.followersCount,
|
||||||
bot: item.isBot,
|
followingCount: item.followingCount,
|
||||||
url: `https://${domain}/@${item.username}`,
|
statusesCount: item.notesCount,
|
||||||
avatar: item.avatarUrl,
|
bot: item.isBot,
|
||||||
locked: item.isLocked,
|
url: `https://${domain}/@${item.username}`,
|
||||||
lastStatusAt: item.updatedAt !== null ? new Date(item.updatedAt) : undefined,
|
avatar: item.avatarUrl ?? undefined,
|
||||||
createdAt: new Date(item.createdAt),
|
locked: item.isLocked,
|
||||||
fields: [
|
lastStatusAt:
|
||||||
...item.fields.map(field => {
|
item.updatedAt !== null ? new Date(item.updatedAt) : undefined,
|
||||||
return {
|
createdAt: new Date(item.createdAt),
|
||||||
name: replaceEmojis(field.name, item.emojis),
|
fields: [
|
||||||
value: replaceEmojis(field.value, item.emojis),
|
...item.fields.map((field) => {
|
||||||
verifiedAt: undefined
|
return {
|
||||||
}
|
name: replaceEmojis(field.name, item.emojis),
|
||||||
}),
|
value: replaceEmojis(field.value, item.emojis),
|
||||||
...[
|
verifiedAt: undefined
|
||||||
{ name: 'Location', value: item.location, verifiedAt: undefined },
|
}
|
||||||
{ name: 'Birthday', value: item.birthday, verifiedAt: undefined },
|
}),
|
||||||
{ name: 'Language', value: item.lang, verifiedAt: undefined }
|
...([
|
||||||
].filter(field => field.value !== null)
|
{ name: 'Location', value: item.location, verifiedAt: undefined },
|
||||||
],
|
{ name: 'Birthday', value: item.birthday, verifiedAt: undefined },
|
||||||
type: 'account',
|
{ name: 'Language', value: item.lang, verifiedAt: undefined }
|
||||||
parentFeed: undefined
|
].filter((field) => field.value !== null) as FieldData[])
|
||||||
}
|
],
|
||||||
|
type: 'account',
|
||||||
|
parentFeed: undefined
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export class NoMoreFeedsError extends Error {
|
export class NoMoreFeedsError extends Error {
|
||||||
public constructor (feedType:string) {
|
public constructor (feedType: string) {
|
||||||
super(`No more feeds of type ${feedType}`)
|
super(`No more feeds of type ${feedType}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export class NoMoreNodesError extends Error {
|
export class NoMoreNodesError extends Error {
|
||||||
public constructor (nodeType:string) {
|
public constructor (nodeType: string) {
|
||||||
super(`No more nodes of type ${nodeType}`)
|
super(`No more nodes of type ${nodeType}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { NodeProviderMethod } from './NodeProviderMethod'
|
import { NodeProviderMethod } from './NodeProviderMethod'
|
||||||
|
|
||||||
export interface NodeProvider {
|
export interface NodeProvider {
|
||||||
getKey:()=>string,
|
getKey: () => string
|
||||||
retrieveNodes: NodeProviderMethod
|
retrieveNodes: NodeProviderMethod
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
|
export type NodeProviderMethod = (
|
||||||
export type NodeProviderMethod = (domain: string, page:number)=> Promise<string[]>
|
domain: string,
|
||||||
|
page: number
|
||||||
|
) => Promise<string[]>
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const avatarSchema = z.optional(z.nullable(z.object({
|
export const avatarSchema = z.optional(
|
||||||
path: z.string()
|
z.nullable(
|
||||||
})))
|
z.object({
|
||||||
|
path: z.string()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
export type Avatar = z.infer<typeof avatarSchema>
|
export type Avatar = z.infer<typeof avatarSchema>
|
||||||
|
|
|
@ -7,16 +7,21 @@ import { retrieveFollowers } from './retrieveFollowers'
|
||||||
|
|
||||||
const PeertubeProvider: Provider = {
|
const PeertubeProvider: Provider = {
|
||||||
getKey: () => 'peertube',
|
getKey: () => 'peertube',
|
||||||
getNodeProviders: ():NodeProvider[] => [{
|
getNodeProviders: (): NodeProvider[] => [
|
||||||
getKey: () => 'followers',
|
{
|
||||||
retrieveNodes: retrieveFollowers
|
getKey: () => 'followers',
|
||||||
}],
|
retrieveNodes: retrieveFollowers
|
||||||
getFeedProviders: ():FeedProvider[] => [{
|
}
|
||||||
getKey: () => 'accounts',
|
],
|
||||||
retrieveFeeds: retrieveAccounts
|
getFeedProviders: (): FeedProvider[] => [
|
||||||
}, {
|
{
|
||||||
getKey: () => 'video-channels',
|
getKey: () => 'accounts',
|
||||||
retrieveFeeds: retrieveVideoChannels
|
retrieveFeeds: retrieveAccounts
|
||||||
}]
|
},
|
||||||
|
{
|
||||||
|
getKey: () => 'video-channels',
|
||||||
|
retrieveFeeds: retrieveVideoChannels
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
export default PeertubeProvider
|
export default PeertubeProvider
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import { Avatar } from './Avatar'
|
import { Avatar } from './Avatar'
|
||||||
|
|
||||||
export const parseAvatarUrl = (data:Avatar, domain:string):string|undefined => {
|
export const parseAvatarUrl = (
|
||||||
if (data === null) {
|
data: Avatar,
|
||||||
|
domain: string
|
||||||
|
): string | undefined => {
|
||||||
|
if (data === null || data === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return `https://${domain}${data.path}`
|
return `https://${domain}${data.path}`
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
export const parseDescription = (description:string|null):string => {
|
export const parseDescription = (description: string | null): string => {
|
||||||
if (typeof description !== 'string') {
|
if (typeof description !== 'string') {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return description.split('\n\n').map(paragraph => {
|
return description
|
||||||
paragraph = paragraph.replace('\n', '</br>\n')
|
.split('\n\n')
|
||||||
return `<p>${paragraph}</p>`
|
.map((paragraph) => {
|
||||||
}).join('\n')
|
paragraph = paragraph.replace('\n', '</br>\n')
|
||||||
|
return `<p>${paragraph}</p>`
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ const schema = z.object({
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const retrieveAccounts:FeedProviderMethod = async (domain, page) => {
|
export const retrieveAccounts: FeedProviderMethod = async (domain, page) => {
|
||||||
const response = await axios.get(`https://${domain}/api/v1/accounts`, {
|
const response = await axios.get(`https://${domain}/api/v1/accounts`, {
|
||||||
params: {
|
params: {
|
||||||
count: limit,
|
count: limit,
|
||||||
|
@ -44,8 +44,8 @@ export const retrieveAccounts:FeedProviderMethod = async (domain, page) => {
|
||||||
throw new NoMoreFeedsError('account')
|
throw new NoMoreFeedsError('account')
|
||||||
}
|
}
|
||||||
return responseData.data
|
return responseData.data
|
||||||
.filter(item => item.host === domain)
|
.filter((item) => item.host === domain)
|
||||||
.map((item):FeedData => {
|
.map((item): FeedData => {
|
||||||
return {
|
return {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
url: item.url ?? `https://${domain}/accounts/${item.name}/`,
|
url: item.url ?? `https://${domain}/accounts/${item.name}/`,
|
||||||
|
|
|
@ -21,19 +21,22 @@ const schema = z.object({
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const retrieveFollowers:NodeProviderMethod = async (domain, page) => {
|
export const retrieveFollowers: NodeProviderMethod = async (domain, page) => {
|
||||||
const response = await axios.get(`https://${domain}/api/v1/server/followers`, {
|
const response = await axios.get(
|
||||||
params: {
|
`https://${domain}/api/v1/server/followers`,
|
||||||
count: limit,
|
{
|
||||||
sort: 'createdAt',
|
params: {
|
||||||
start: page * limit
|
count: limit,
|
||||||
},
|
sort: 'createdAt',
|
||||||
timeout: getDefaultTimeoutMilliseconds()
|
start: page * limit
|
||||||
})
|
},
|
||||||
|
timeout: getDefaultTimeoutMilliseconds()
|
||||||
|
}
|
||||||
|
)
|
||||||
assertSuccessJsonResponse(response)
|
assertSuccessJsonResponse(response)
|
||||||
const responseData = schema.parse(response.data)
|
const responseData = schema.parse(response.data)
|
||||||
const hosts = new Set<string>()
|
const hosts = new Set<string>()
|
||||||
responseData.data.forEach(item => {
|
responseData.data.forEach((item) => {
|
||||||
hosts.add(item.follower.host)
|
hosts.add(item.follower.host)
|
||||||
hosts.add(item.following.host)
|
hosts.add(item.following.host)
|
||||||
})
|
})
|
||||||
|
|
|
@ -36,7 +36,10 @@ const schema = z.object({
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const retrieveVideoChannels:FeedProviderMethod = async (domain, page) => {
|
export const retrieveVideoChannels: FeedProviderMethod = async (
|
||||||
|
domain,
|
||||||
|
page
|
||||||
|
) => {
|
||||||
const response = await axios.get(`https://${domain}/api/v1/video-channels`, {
|
const response = await axios.get(`https://${domain}/api/v1/video-channels`, {
|
||||||
params: {
|
params: {
|
||||||
count: limit,
|
count: limit,
|
||||||
|
@ -51,17 +54,18 @@ export const retrieveVideoChannels:FeedProviderMethod = async (domain, page) =>
|
||||||
throw new NoMoreFeedsError('channel')
|
throw new NoMoreFeedsError('channel')
|
||||||
}
|
}
|
||||||
return responseData.data
|
return responseData.data
|
||||||
.filter(item => item.host === domain)
|
.filter((item) => item.host === domain)
|
||||||
.map((item):FeedData => {
|
.map((item): FeedData => {
|
||||||
const fields:FieldData[] = item.support
|
const fields: FieldData[] =
|
||||||
? [{ name: 'support', value: item.support, verifiedAt: undefined }]
|
item.support !== null
|
||||||
: []
|
? [{ name: 'support', value: item.support, verifiedAt: undefined }]
|
||||||
|
: []
|
||||||
return {
|
return {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
url: item.url ?? `https://${domain}/video-channels/${item.name}/`,
|
url: item.url ?? `https://${domain}/video-channels/${item.name}/`,
|
||||||
avatar: parseAvatarUrl(item.avatar, domain),
|
avatar: parseAvatarUrl(item.avatar, domain),
|
||||||
locked: false,
|
locked: false,
|
||||||
fields: fields,
|
fields,
|
||||||
description: parseDescription(item.description),
|
description: parseDescription(item.description),
|
||||||
displayName: item.displayName,
|
displayName: item.displayName,
|
||||||
followersCount: item.followersCount,
|
followersCount: item.followersCount,
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { NodeProvider } from './NodeProvider'
|
||||||
import { FeedProvider } from './FeedProvider'
|
import { FeedProvider } from './FeedProvider'
|
||||||
|
|
||||||
export interface Provider {
|
export interface Provider {
|
||||||
getKey(): string
|
getKey: () => string
|
||||||
|
|
||||||
getNodeProviders(): NodeProvider[]
|
getNodeProviders: () => NodeProvider[]
|
||||||
|
|
||||||
getFeedProviders(): FeedProvider[]
|
getFeedProviders: () => FeedProvider[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export class ProviderKeyAlreadyRegisteredError extends Error {
|
export class ProviderKeyAlreadyRegisteredError extends Error {
|
||||||
private readonly _key:string
|
private readonly _key: string
|
||||||
|
|
||||||
public constructor (key:string) {
|
public constructor (key: string) {
|
||||||
super(`Provider with the key ${key} is already registered`)
|
super(`Provider with the key ${key} is already registered`)
|
||||||
this._key = key
|
this._key = key
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
export class ProviderKeyNotFoundError extends Error {
|
||||||
|
private readonly _key: string
|
||||||
|
|
||||||
|
public constructor (key: string) {
|
||||||
|
super(`Provider with the key ${key} is not registered`)
|
||||||
|
this._key = key
|
||||||
|
}
|
||||||
|
|
||||||
|
public get key (): string {
|
||||||
|
return this._key
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
import { Provider } from './Provider'
|
import { Provider } from './Provider'
|
||||||
import { Dictionary } from 'typescript-collections'
|
import { Dictionary } from 'typescript-collections'
|
||||||
import { ProviderKeyAlreadyRegisteredError } from './ProviderKeyAlreadyRegisteredError'
|
import { ProviderKeyAlreadyRegisteredError } from './ProviderKeyAlreadyRegisteredError'
|
||||||
|
import { ProviderKeyNotFoundError } from './ProviderKeyNotFoundError'
|
||||||
export interface ProviderCallback {
|
export type ProviderCallback = (key: string, provider: Provider) => void
|
||||||
(key: string, provider: Provider): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const providers: Dictionary<string, Provider> = new Dictionary<string, Provider>()
|
const providers: Dictionary<string, Provider> = new Dictionary<string, Provider>()
|
||||||
|
|
||||||
|
@ -14,14 +12,18 @@ const registerProvider = (provider: Provider): void => {
|
||||||
throw new ProviderKeyAlreadyRegisteredError(key)
|
throw new ProviderKeyAlreadyRegisteredError(key)
|
||||||
}
|
}
|
||||||
providers.setValue(key, provider)
|
providers.setValue(key, provider)
|
||||||
console.info('Added provider to registry', { key: key })
|
console.info('Added provider to registry', { key })
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProviderByKey = (key: string): Provider => {
|
const getProviderByKey = (key: string): Provider => {
|
||||||
return providers.getValue(key)
|
const provider = providers.getValue(key)
|
||||||
|
if (provider === undefined) {
|
||||||
|
throw new ProviderKeyNotFoundError(key)
|
||||||
|
}
|
||||||
|
return provider
|
||||||
}
|
}
|
||||||
|
|
||||||
const getKeys = ():string[] => {
|
const getKeys = (): string[] => {
|
||||||
return providers.keys()
|
return providers.keys()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,19 +31,19 @@ const forEachProvider = (callback: ProviderCallback): void => {
|
||||||
return providers.forEach(callback)
|
return providers.forEach(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
const containsKey = (key:string):boolean => {
|
const containsKey = (key: string): boolean => {
|
||||||
return providers.containsKey(key)
|
return providers.containsKey(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderRegistry {
|
export interface ProviderRegistry {
|
||||||
registerProvider: (provider: Provider)=> void,
|
registerProvider: (provider: Provider) => void
|
||||||
getProviderByKey:(key: string)=> Provider
|
getProviderByKey: (key: string) => Provider
|
||||||
forEachProvider:(callback: ProviderCallback)=> void
|
forEachProvider: (callback: ProviderCallback) => void
|
||||||
getKeys:()=>string[]
|
getKeys: () => string[]
|
||||||
containsKey:(key:string)=>boolean
|
containsKey: (key: string) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const providerRegistry:ProviderRegistry = {
|
export const providerRegistry: ProviderRegistry = {
|
||||||
registerProvider,
|
registerProvider,
|
||||||
getProviderByKey,
|
getProviderByKey,
|
||||||
forEachProvider,
|
forEachProvider,
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
import { UnexpectedResponseError } from './UnexpectedResponseError'
|
import { UnexpectedResponseError } from './UnexpectedResponseError'
|
||||||
|
|
||||||
export class UnexpectedContentTypeError extends UnexpectedResponseError {
|
export class UnexpectedContentTypeError extends UnexpectedResponseError {
|
||||||
private readonly _expectedContentType: string
|
private readonly _expectedContentType: string
|
||||||
private readonly _actualContentType: string
|
private readonly _actualContentType: string
|
||||||
|
|
||||||
public constructor (actualContentType: string, expectedContentType:string) {
|
public constructor (actualContentType: string, expectedContentType: string) {
|
||||||
super(`Expected content type '${expectedContentType}' but got '${actualContentType}'`)
|
super(
|
||||||
this._expectedContentType = expectedContentType
|
`Expected content type '${expectedContentType}' but got '${actualContentType}'`
|
||||||
this._actualContentType = actualContentType
|
)
|
||||||
}
|
this._expectedContentType = expectedContentType
|
||||||
|
this._actualContentType = actualContentType
|
||||||
|
}
|
||||||
|
|
||||||
get expectedContentType (): string {
|
get expectedContentType (): string {
|
||||||
return this._expectedContentType
|
return this._expectedContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
get actualContentType (): string {
|
get actualContentType (): string {
|
||||||
return this._actualContentType
|
return this._actualContentType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
export class UnexpectedResponseError extends Error {
|
export class UnexpectedResponseError extends Error {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
import { UnexpectedResponseError } from './UnexpectedResponseError'
|
import { UnexpectedResponseError } from './UnexpectedResponseError'
|
||||||
|
|
||||||
export class UnexpectedResponseStatusError extends UnexpectedResponseError {
|
export class UnexpectedResponseStatusError extends UnexpectedResponseError {
|
||||||
private readonly _expectedStatusCode: number
|
private readonly _expectedStatusCode: number
|
||||||
private readonly _actualStatusCode: number
|
private readonly _actualStatusCode: number
|
||||||
|
|
||||||
public constructor (expectedStatusCode:number, actualStatusCode:number) {
|
public constructor (expectedStatusCode: number, actualStatusCode: number) {
|
||||||
super(`Expected response code ${expectedStatusCode} but got ${actualStatusCode}`)
|
super(
|
||||||
this._actualStatusCode = actualStatusCode
|
`Expected response code ${expectedStatusCode} but got ${actualStatusCode}`
|
||||||
this._expectedStatusCode = expectedStatusCode
|
)
|
||||||
}
|
this._actualStatusCode = actualStatusCode
|
||||||
|
this._expectedStatusCode = expectedStatusCode
|
||||||
|
}
|
||||||
|
|
||||||
get expectedStatusCode (): number {
|
get expectedStatusCode (): number {
|
||||||
return this._expectedStatusCode
|
return this._expectedStatusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
get actualStatusCode (): number {
|
get actualStatusCode (): number {
|
||||||
return this._actualStatusCode
|
return this._actualStatusCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { AxiosResponse } from 'axios'
|
||||||
import { UnexpectedResponseStatusError } from './UnexpectedResponseStatusError'
|
import { UnexpectedResponseStatusError } from './UnexpectedResponseStatusError'
|
||||||
import { UnexpectedContentTypeError } from './UnexpectedContentTypeError'
|
import { UnexpectedContentTypeError } from './UnexpectedContentTypeError'
|
||||||
|
|
||||||
export const assertSuccessJsonResponse = (response: AxiosResponse<unknown>): void => {
|
export const assertSuccessJsonResponse = (
|
||||||
|
response: AxiosResponse<unknown>
|
||||||
|
): void => {
|
||||||
const expectedStatus = 200
|
const expectedStatus = 200
|
||||||
const actualStatus = response.status
|
const actualStatus = response.status
|
||||||
if (actualStatus !== expectedStatus) {
|
if (actualStatus !== expectedStatus) {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export const getDefaultTimeoutMilliseconds = () :number => {
|
export const getDefaultTimeoutMilliseconds = (): number => {
|
||||||
return parseInt(process.env.DEFAULT_TIMEOUT_MILLISECONDS ?? '10000')
|
return parseInt(process.env.DEFAULT_TIMEOUT_MILLISECONDS ?? '10000')
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,20 @@ import { updateNodeIps } from '../../Storage/Nodes/updateNodeIps'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
import Node from '../../Storage/Definitions/Node'
|
import Node from '../../Storage/Definitions/Node'
|
||||||
|
|
||||||
const refreshNodeIps = async (elastic: ElasticClient, node:Node):Promise<Node> => {
|
const refreshNodeIps = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
node: Node
|
||||||
|
): Promise<Node> => {
|
||||||
console.info('Looking up node ip addresses', {
|
console.info('Looking up node ip addresses', {
|
||||||
nodeDomain: node.domain
|
nodeDomain: node.domain
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
const addresses = await lookup(node.domain, { all: true })
|
const addresses = await lookup(node.domain, { all: true })
|
||||||
return updateNodeIps(elastic, node, addresses.map(resolution => resolution.address))
|
return await updateNodeIps(
|
||||||
|
elastic,
|
||||||
|
node,
|
||||||
|
addresses.map((resolution) => resolution.address)
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Could not lookup the domain', { node, error })
|
console.warn('Could not lookup the domain', { node, error })
|
||||||
return node
|
return node
|
||||||
|
|
|
@ -7,9 +7,17 @@ import Feed from '../../Storage/Definitions/Feed'
|
||||||
import Node from '../../Storage/Definitions/Node'
|
import Node from '../../Storage/Definitions/Node'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
|
|
||||||
export const addFeed = async (elastic: ElasticClient, node: Node, feedData: FeedData): Promise<Feed> => {
|
export const addFeed = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
node: Node,
|
||||||
|
feedData: FeedData
|
||||||
|
): Promise<Feed> => {
|
||||||
const fulltext = prepareFulltext(feedData, node)
|
const fulltext = prepareFulltext(feedData, node)
|
||||||
const extractedTags = extractTags(fulltext)
|
const extractedTags = extractTags(fulltext)
|
||||||
const extractedEmails = extractEmails(fulltext)
|
const extractedEmails = extractEmails(fulltext)
|
||||||
return await createFeed(elastic, { ...feedData, extractedTags, extractedEmails }, node)
|
return await createFeed(
|
||||||
|
elastic,
|
||||||
|
{ ...feedData, extractedTags, extractedEmails },
|
||||||
|
node
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,21 @@ import { FeedData } from '../../Fediverse/Providers/FeedData'
|
||||||
import striptags from 'striptags'
|
import striptags from 'striptags'
|
||||||
import Node from '../../Storage/Definitions/Node'
|
import Node from '../../Storage/Definitions/Node'
|
||||||
|
|
||||||
export default function (feedData: FeedData, node: Node):string {
|
export default function (feedData: FeedData, node: Node): string {
|
||||||
return striptags(
|
return striptags(
|
||||||
feedData.displayName +
|
feedData.displayName +
|
||||||
' ' + feedData.description +
|
' ' +
|
||||||
' ' + feedData.fields.map(field => field.name).join(' ') +
|
feedData.description +
|
||||||
' ' + feedData.fields.map(field => field.value).join(' ') +
|
' ' +
|
||||||
' ' + feedData.name + '@' + node.domain +
|
feedData.fields.map((field) => field.name).join(' ') +
|
||||||
(feedData.parentFeed ? (' ' + feedData.parentFeed.name + '@' + feedData.parentFeed.hostDomain) : '')
|
' ' +
|
||||||
|
feedData.fields.map((field) => field.value).join(' ') +
|
||||||
|
' ' +
|
||||||
|
feedData.name +
|
||||||
|
'@' +
|
||||||
|
node.domain +
|
||||||
|
(feedData.parentFeed != null
|
||||||
|
? ' ' + feedData.parentFeed.name + '@' + feedData.parentFeed.hostDomain
|
||||||
|
: '')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,20 @@ import Node from '../../Storage/Definitions/Node'
|
||||||
import prepareFulltext from './prepareFulltext'
|
import prepareFulltext from './prepareFulltext'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
|
|
||||||
export const refreshFeed = async (elastic: ElasticClient, feed:Feed, feedData: FeedData, node: Node): Promise<Feed> => {
|
export const refreshFeed = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
feed: Feed,
|
||||||
|
feedData: FeedData,
|
||||||
|
node: Node
|
||||||
|
): Promise<Feed> => {
|
||||||
const fulltext = prepareFulltext(feedData, node)
|
const fulltext = prepareFulltext(feedData, node)
|
||||||
|
|
||||||
const extractedTags = extractTags(fulltext)
|
const extractedTags = extractTags(fulltext)
|
||||||
const extractedEmails = extractEmails(fulltext)
|
const extractedEmails = extractEmails(fulltext)
|
||||||
|
|
||||||
return await updateFeed(elastic, feed, { ...feedData, extractedTags, extractedEmails })
|
return await updateFeed(elastic, feed, {
|
||||||
|
...feedData,
|
||||||
|
extractedTags,
|
||||||
|
extractedEmails
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,26 @@ import { FeedProvider } from '../../Fediverse/Providers/FeedProvider'
|
||||||
import Node from '../../Storage/Definitions/Node'
|
import Node from '../../Storage/Definitions/Node'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
|
|
||||||
export const refreshFeeds = async (elastic: ElasticClient, provider:FeedProvider, node:Node):Promise<void> => {
|
export const refreshFeeds = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
provider: FeedProvider,
|
||||||
|
node: Node
|
||||||
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
// noinspection InfiniteLoopJS
|
||||||
for (let page = 0; true; page++) {
|
for (let page = 0; true; page++) {
|
||||||
console.info('Retrieve feeds page', { nodeDomain: node.domain, provider: provider.getKey(), page: page })
|
console.info('Retrieve feeds page', {
|
||||||
|
nodeDomain: node.domain,
|
||||||
|
provider: provider.getKey(),
|
||||||
|
page
|
||||||
|
})
|
||||||
await refreshFeedsOnPage(elastic, provider, node, page)
|
await refreshFeedsOnPage(elastic, provider, node, page)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.info('Feed search finished: ' + e, { nodeDomain: node.domain, provider: provider.getKey() })
|
console.info('Feed search finished', {
|
||||||
|
error,
|
||||||
|
nodeDomain: node.domain,
|
||||||
|
provider: provider.getKey()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,23 @@ import Node from '../../Storage/Definitions/Node'
|
||||||
import Feed from '../../Storage/Definitions/Feed'
|
import Feed from '../../Storage/Definitions/Feed'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
|
|
||||||
export const refreshFeedsOnPage = async (elastic: ElasticClient, provider:FeedProvider, node:Node, page:number):Promise<Feed[]> => {
|
export const refreshFeedsOnPage = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
provider: FeedProvider,
|
||||||
|
node: Node,
|
||||||
|
page: number
|
||||||
|
): Promise<Feed[]> => {
|
||||||
const feedData = await provider.retrieveFeeds(node.domain, page)
|
const feedData = await provider.retrieveFeeds(node.domain, page)
|
||||||
console.info('Retrieved feeds', { count: feedData.length, domain: node.domain, provider: provider.getKey(), page: page })
|
console.info('Retrieved feeds', {
|
||||||
return Promise.all(feedData.map(
|
count: feedData.length,
|
||||||
feedDataItem => refreshOrAddFeed(elastic, node, feedDataItem)
|
domain: node.domain,
|
||||||
))
|
provider: provider.getKey(),
|
||||||
|
page
|
||||||
|
})
|
||||||
|
return await Promise.all(
|
||||||
|
feedData.map(
|
||||||
|
async (feedDataItem) =>
|
||||||
|
await refreshOrAddFeed(elastic, node, feedDataItem)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,17 +6,27 @@ import Node from '../../Storage/Definitions/Node'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
import getFeed from '../../Storage/Feeds/getFeed'
|
import getFeed from '../../Storage/Feeds/getFeed'
|
||||||
|
|
||||||
export const refreshOrAddFeed = async (elastic: ElasticClient, node:Node, feedData:FeedData):Promise<Feed> => {
|
export const refreshOrAddFeed = async (
|
||||||
let feed:Feed|null = null
|
elastic: ElasticClient,
|
||||||
|
node: Node,
|
||||||
|
feedData: FeedData
|
||||||
|
): Promise<Feed> => {
|
||||||
|
let feed: Feed | null | undefined
|
||||||
try {
|
try {
|
||||||
feed = await getFeed(elastic, `${feedData.name}@${node.domain}`)
|
feed = await getFeed(elastic, `${feedData.name}@${node.domain}`)
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
|
if (feed !== null && feed !== undefined) {
|
||||||
}
|
console.info('Refreshing feed', {
|
||||||
if (feed) {
|
nodeDomain: node.domain,
|
||||||
console.info('Refreshing feed', { nodeDomain: node.domain, feedName: feedData.name, feedType: feedData.type })
|
feedName: feedData.name,
|
||||||
|
feedType: feedData.type
|
||||||
|
})
|
||||||
return await refreshFeed(elastic, feed, feedData, node)
|
return await refreshFeed(elastic, feed, feedData, node)
|
||||||
}
|
}
|
||||||
console.info('Adding feed', { nodeDomain: node.domain, feedName: feedData.name, feedType: feedData.type })
|
console.info('Adding feed', {
|
||||||
|
nodeDomain: node.domain,
|
||||||
|
feedName: feedData.name,
|
||||||
|
feedType: feedData.type
|
||||||
|
})
|
||||||
return await addFeed(elastic, node, feedData)
|
return await addFeed(elastic, node, feedData)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,16 @@ import { updateNodeInfo } from '../../Storage/Nodes/updateNodeInfo'
|
||||||
import Node from '../../Storage/Definitions/Node'
|
import Node from '../../Storage/Definitions/Node'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
|
|
||||||
export const refreshNodeInfo = async (elastic: ElasticClient, node:Node):Promise<Node> => {
|
export const refreshNodeInfo = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
node: Node
|
||||||
|
): Promise<Node> => {
|
||||||
console.info('Updating info of node', { nodeDomain: node.domain })
|
console.info('Updating info of node', { nodeDomain: node.domain })
|
||||||
try {
|
try {
|
||||||
const nodeInfo = await retrieveDomainNodeInfo(node.domain)
|
const nodeInfo = await retrieveDomainNodeInfo(node.domain)
|
||||||
return await updateNodeInfo(elastic, node, nodeInfo)
|
return await updateNodeInfo(elastic, node, nodeInfo)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to update node info: ' + error)
|
console.warn('Failed to update node info', error)
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,25 @@ import { findNewNodesOnPage } from './findNewNodesOnPage'
|
||||||
import Node from '../../Storage/Definitions/Node'
|
import Node from '../../Storage/Definitions/Node'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
|
|
||||||
export const findNewNodes = async (elastic: ElasticClient, provider:NodeProvider, node:Node):Promise<void> => {
|
export const findNewNodes = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
provider: NodeProvider,
|
||||||
|
node: Node
|
||||||
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
// noinspection InfiniteLoopJS
|
||||||
for (let page = 0; true; page++) {
|
for (let page = 0; true; page++) {
|
||||||
console.info('Retrieve node page', { domain: node.domain, provider: provider.getKey() })
|
console.info('Retrieve node page', {
|
||||||
|
domain: node.domain,
|
||||||
|
provider: provider.getKey()
|
||||||
|
})
|
||||||
await findNewNodesOnPage(elastic, provider, node, page)
|
await findNewNodesOnPage(elastic, provider, node, page)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.info('Node search finished: ' + e, { domain: node.domain, provider: provider.getKey() })
|
console.info('Node search finished', {
|
||||||
|
error,
|
||||||
|
domain: node.domain,
|
||||||
|
provider: provider.getKey()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,18 @@ import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
import isDomainNotBanned from '../../Storage/Nodes/isDomainNotBanned'
|
import isDomainNotBanned from '../../Storage/Nodes/isDomainNotBanned'
|
||||||
|
|
||||||
export const findNewNodesOnPage = async (
|
export const findNewNodesOnPage = async (
|
||||||
elastic: ElasticClient, provider: NodeProvider, node:Node, page:number
|
elastic: ElasticClient,
|
||||||
):Promise<number> => {
|
provider: NodeProvider,
|
||||||
|
node: Node,
|
||||||
|
page: number
|
||||||
|
): Promise<number> => {
|
||||||
let domains = await provider.retrieveNodes(node.domain, page)
|
let domains = await provider.retrieveNodes(node.domain, page)
|
||||||
domains = domains.filter(isDomainNotBanned)
|
domains = domains.filter(isDomainNotBanned)
|
||||||
console.log('Found nodes', { count: domains.length, domain: node.domain, provider: provider.getKey(), page: page })
|
console.log('Found nodes', {
|
||||||
|
count: domains.length,
|
||||||
|
domain: node.domain,
|
||||||
|
provider: provider.getKey(),
|
||||||
|
page
|
||||||
|
})
|
||||||
return await createMissingNodes(elastic, domains, node.domain)
|
return await createMissingNodes(elastic, domains, node.domain)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,15 @@ import { setNodeStats } from '../../Storage/Nodes/setNodeStats'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
import Node from '../../Storage/Definitions/Node'
|
import Node from '../../Storage/Definitions/Node'
|
||||||
|
|
||||||
export type NodeStats ={
|
export interface NodeStats {
|
||||||
account: number,
|
account: number
|
||||||
channel: number,
|
channel: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function updateNodeFeedStats (elasticClient: ElasticClient, node: Node) {
|
export default async function updateNodeFeedStats (
|
||||||
|
elasticClient: ElasticClient,
|
||||||
|
node: Node
|
||||||
|
): Promise<void> {
|
||||||
await setNodeStats(
|
await setNodeStats(
|
||||||
elasticClient,
|
elasticClient,
|
||||||
node,
|
node,
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { createMissingNodes } from '../../Storage/Nodes/createMissingNodes'
|
import { createMissingNodes } from '../../Storage/Nodes/createMissingNodes'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
|
|
||||||
export const addNodeSeed = async (elastic: ElasticClient, domains:string[]):Promise<boolean> => {
|
export const addNodeSeed = async (
|
||||||
console.info('Trying to add seed domain nodes', { domains: domains })
|
elastic: ElasticClient,
|
||||||
|
domains: string[]
|
||||||
|
): Promise<boolean> => {
|
||||||
|
console.info('Trying to add seed domain nodes', { domains })
|
||||||
const result = await createMissingNodes(elastic, domains, undefined)
|
const result = await createMissingNodes(elastic, domains, undefined)
|
||||||
return result > 0
|
return result > 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,13 @@ import { deleteDomainFeeds } from '../../Storage/Feeds/deleteDomainFeeds'
|
||||||
import { deleteDomainNodes } from '../../Storage/Nodes/deleteDomainNodes'
|
import { deleteDomainNodes } from '../../Storage/Nodes/deleteDomainNodes'
|
||||||
import { ElasticClient } from '../../Storage/ElasticClient'
|
import { ElasticClient } from '../../Storage/ElasticClient'
|
||||||
|
|
||||||
export default async function deleteDomains (elastic: ElasticClient, domains:string[]):Promise<number> {
|
export default async function deleteDomains (
|
||||||
if (domains === []) {
|
elastic: ElasticClient,
|
||||||
return
|
domains: string[]
|
||||||
|
): Promise<number> {
|
||||||
|
if (domains.length === 0) {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
await deleteDomainFeeds(elastic, domains)
|
await deleteDomainFeeds(elastic, domains)
|
||||||
return deleteDomainNodes(elastic, domains)
|
return await deleteDomainNodes(elastic, domains)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export default function getBannedDomains ():string[] {
|
export default function getBannedDomains (): string[] {
|
||||||
const domains = process.env.BANNED_DOMAINS ?? ''
|
const domains = process.env.BANNED_DOMAINS ?? ''
|
||||||
if (domains === '') {
|
if (domains === '') {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return domains.split(',').map(domain => domain.toLowerCase())
|
return domains.split(',').map((domain) => domain.toLowerCase())
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,10 @@ import refreshNodeIps from './Dns/refreshNodeIps'
|
||||||
import { ElasticClient } from '../Storage/ElasticClient'
|
import { ElasticClient } from '../Storage/ElasticClient'
|
||||||
import updateNodeFeedStats from './Nodes/updateNodeFeedStats'
|
import updateNodeFeedStats from './Nodes/updateNodeFeedStats'
|
||||||
|
|
||||||
export const processNextNode = async (elastic: ElasticClient, providerRegistry:ProviderRegistry):Promise<void> => {
|
export const processNextNode = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
providerRegistry: ProviderRegistry
|
||||||
|
): Promise<void> => {
|
||||||
console.info('#############################################')
|
console.info('#############################################')
|
||||||
let node = await fetchNodeToProcess(elastic)
|
let node = await fetchNodeToProcess(elastic)
|
||||||
node = await setNodeRefreshAttempted(elastic, node)
|
node = await setNodeRefreshAttempted(elastic, node)
|
||||||
|
@ -20,25 +23,35 @@ export const processNextNode = async (elastic: ElasticClient, providerRegistry:P
|
||||||
node = await refreshNodeIps(elastic, node)
|
node = await refreshNodeIps(elastic, node)
|
||||||
node = await refreshNodeInfo(elastic, node)
|
node = await refreshNodeInfo(elastic, node)
|
||||||
|
|
||||||
if (!providerRegistry.containsKey(node.softwareName)) {
|
const softwareName = node.softwareName ?? ''
|
||||||
console.warn('Unknown software', { domain: node.domain, software: node.softwareName })
|
if (!providerRegistry.containsKey(softwareName)) {
|
||||||
|
console.warn('Unknown software', {
|
||||||
|
domain: node.domain,
|
||||||
|
software: node.softwareName
|
||||||
|
})
|
||||||
await deleteOldFeeds(elastic, node)
|
await deleteOldFeeds(elastic, node)
|
||||||
await setNodeRefreshed(elastic, node)
|
await setNodeRefreshed(elastic, node)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const provider = providerRegistry.getProviderByKey(node.softwareName)
|
const provider = providerRegistry.getProviderByKey(softwareName)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
provider.getNodeProviders().map((nodeProvider:NodeProvider) => {
|
provider.getNodeProviders().map(async (nodeProvider: NodeProvider) => {
|
||||||
console.info('Searching for nodes', { domain: node.domain, provider: nodeProvider.getKey() })
|
console.info('Searching for nodes', {
|
||||||
return findNewNodes(elastic, nodeProvider, node)
|
domain: node.domain,
|
||||||
|
provider: nodeProvider.getKey()
|
||||||
|
})
|
||||||
|
return await findNewNodes(elastic, nodeProvider, node)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
provider.getFeedProviders().map((feedProvider:FeedProvider) => {
|
provider.getFeedProviders().map(async (feedProvider: FeedProvider) => {
|
||||||
console.info('Searching for feeds', { domain: node.domain, provider: feedProvider.getKey() })
|
console.info('Searching for feeds', {
|
||||||
return refreshFeeds(elastic, feedProvider, node)
|
domain: node.domain,
|
||||||
|
provider: feedProvider.getKey()
|
||||||
|
})
|
||||||
|
return await refreshFeeds(elastic, feedProvider, node)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
import Field from './Field'
|
import Field from './Field'
|
||||||
|
|
||||||
interface Feed {
|
interface Feed {
|
||||||
domain: string
|
domain: string
|
||||||
foundAt: number,
|
foundAt: number
|
||||||
refreshedAt?: number,
|
refreshedAt?: number
|
||||||
name: string,
|
name: string
|
||||||
fullName: string,
|
fullName: string
|
||||||
displayName: string,
|
displayName: string
|
||||||
description: string,
|
description: string
|
||||||
strippedDescription?: string,
|
strippedDescription?: string
|
||||||
followersCount?: number,
|
followersCount?: number
|
||||||
followingCount?: number,
|
followingCount?: number
|
||||||
statusesCount?: number,
|
statusesCount?: number
|
||||||
lastStatusAt?: number,
|
lastStatusAt?: number
|
||||||
createdAt?: number,
|
createdAt?: number
|
||||||
bot?: boolean,
|
bot?: boolean
|
||||||
locked: boolean,
|
locked: boolean
|
||||||
url: string,
|
url: string
|
||||||
avatar?: string,
|
avatar?: string
|
||||||
type: 'account' | 'channel',
|
type: 'account' | 'channel'
|
||||||
parentFeedName?: string,
|
parentFeedName?: string
|
||||||
parentFeedDomain?: string
|
parentFeedDomain?: string
|
||||||
fields: Field[],
|
fields: Field[]
|
||||||
extractedEmails: string[],
|
extractedEmails: string[]
|
||||||
extractedTags: string[]
|
extractedTags: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Feed
|
export default Feed
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
interface Field {
|
interface Field {
|
||||||
name: string,
|
name: string
|
||||||
value: string
|
value: string
|
||||||
strippedName?: string
|
strippedName?: string
|
||||||
strippedValue?: string
|
strippedValue?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Field
|
export default Field
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
interface Geo {
|
interface Geo {
|
||||||
cityName?: string,
|
cityName?: string
|
||||||
continentName?: string,
|
continentName?: string
|
||||||
countryIsoCode?: string,
|
countryIsoCode?: string
|
||||||
countryName?: string,
|
countryName?: string
|
||||||
latitude: number
|
latitude: number
|
||||||
longitude: number
|
longitude: number
|
||||||
location?: string,
|
location?: string
|
||||||
regionIsoCode?: string,
|
regionIsoCode?: string
|
||||||
regionName?: string
|
regionName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Geo
|
export default Geo
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import Geo from './Geo'
|
import Geo from './Geo'
|
||||||
|
|
||||||
interface Node {
|
interface Node {
|
||||||
name?:string,
|
name?: string
|
||||||
strippedName?:string,
|
strippedName?: string
|
||||||
foundAt: number,
|
foundAt: number
|
||||||
refreshAttemptedAt?: number
|
refreshAttemptedAt?: number
|
||||||
refreshedAt?: number
|
refreshedAt?: number
|
||||||
openRegistrations?: boolean
|
openRegistrations?: boolean
|
||||||
domain: string,
|
domain: string
|
||||||
serverIps?: string[],
|
serverIps?: string[]
|
||||||
geoip?: Geo[],
|
geoip?: Geo[]
|
||||||
softwareName?: string;
|
softwareName?: string
|
||||||
softwareVersion?: string
|
softwareVersion?: string
|
||||||
standardizedSoftwareVersion?: string
|
standardizedSoftwareVersion?: string
|
||||||
halfYearActiveUserCount?: number,
|
halfYearActiveUserCount?: number
|
||||||
monthActiveUserCount?: number,
|
monthActiveUserCount?: number
|
||||||
statusesCount?: number,
|
statusesCount?: number
|
||||||
totalUserCount?: number,
|
totalUserCount?: number
|
||||||
discoveredByDomain?:string,
|
discoveredByDomain?: string
|
||||||
accountFeedCount?: number,
|
accountFeedCount?: number
|
||||||
channelFeedCount?: number,
|
channelFeedCount?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Node
|
export default Node
|
||||||
|
|
|
@ -6,7 +6,7 @@ const elasticClient = new Client({
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
username: process.env.ELASTIC_USER ?? 'elastic',
|
username: process.env.ELASTIC_USER ?? 'elastic',
|
||||||
password: process.env.ELASTIC_PASSWORD
|
password: process.env.ELASTIC_PASSWORD ?? ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { FeedData } from '../../Fediverse/Providers/FeedData'
|
import { FeedData } from '../../Fediverse/Providers/FeedData'
|
||||||
|
|
||||||
export default interface StorageFeedData extends FeedData{
|
export default interface StorageFeedData extends FeedData {
|
||||||
extractedTags:string[],
|
extractedTags: string[]
|
||||||
extractedEmails:string[],
|
extractedEmails: string[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const assertFeedIndex = async (elastic: ElasticClient): Promise<void> => {
|
||||||
await elastic.ingest.putPipeline({
|
await elastic.ingest.putPipeline({
|
||||||
id: 'feed',
|
id: 'feed',
|
||||||
description: 'Default feed pipeline',
|
description: 'Default feed pipeline',
|
||||||
processors: processors
|
processors
|
||||||
})
|
})
|
||||||
console.info('Checking feed index')
|
console.info('Checking feed index')
|
||||||
const exists = await elastic.indices.exists({
|
const exists = await elastic.indices.exists({
|
||||||
|
@ -31,12 +31,8 @@ const assertFeedIndex = async (elastic: ElasticClient): Promise<void> => {
|
||||||
html: {
|
html: {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
tokenizer: 'standard',
|
tokenizer: 'standard',
|
||||||
filter: [
|
filter: ['lowercase'],
|
||||||
'lowercase'
|
char_filter: ['html_strip']
|
||||||
],
|
|
||||||
char_filter: [
|
|
||||||
'html_strip'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,18 @@ import Feed from '../Definitions/Feed'
|
||||||
import feedIndex from '../Definitions/feedIndex'
|
import feedIndex from '../Definitions/feedIndex'
|
||||||
import { NodeStats } from '../../Jobs/Nodes/updateNodeFeedStats'
|
import { NodeStats } from '../../Jobs/Nodes/updateNodeFeedStats'
|
||||||
|
|
||||||
type Aggregation = {
|
interface Aggregation {
|
||||||
buckets:{
|
buckets: Array<{
|
||||||
key:'account'|'channel',
|
key: 'account' | 'channel'
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
doc_count:number
|
doc_count: number
|
||||||
}[]
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function countNodeFeeds (elastic: ElasticClient, domain:string):Promise<NodeStats> {
|
export default async function countNodeFeeds (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
domain: string
|
||||||
|
): Promise<NodeStats> {
|
||||||
await elastic.indices.refresh({ index: feedIndex })
|
await elastic.indices.refresh({ index: feedIndex })
|
||||||
const response = await elastic.search<Feed>({
|
const response = await elastic.search<Feed>({
|
||||||
index: feedIndex,
|
index: feedIndex,
|
||||||
|
@ -27,12 +30,12 @@ export default async function countNodeFeeds (elastic: ElasticClient, domain:str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const types = response.aggregations.types as Aggregation
|
const types = response?.aggregations?.types as Aggregation
|
||||||
const result:NodeStats = {
|
const result: NodeStats = {
|
||||||
channel: 0,
|
channel: 0,
|
||||||
account: 0
|
account: 0
|
||||||
}
|
}
|
||||||
types.buckets.forEach(item => {
|
types.buckets.forEach((item) => {
|
||||||
result[item.key] += item.doc_count
|
result[item.key] += item.doc_count
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -4,8 +4,13 @@ import feedIndex from '../Definitions/feedIndex'
|
||||||
import getFeed from './getFeed'
|
import getFeed from './getFeed'
|
||||||
import Feed from '../Definitions/Feed'
|
import Feed from '../Definitions/Feed'
|
||||||
import Node from '../Definitions/Node'
|
import Node from '../Definitions/Node'
|
||||||
|
import assertDefined from '../assertDefined'
|
||||||
|
|
||||||
export const createFeed = async (elastic: ElasticClient, feedData: StorageFeedData, node: Node): Promise<Feed> => {
|
export const createFeed = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
feedData: StorageFeedData,
|
||||||
|
node: Node
|
||||||
|
): Promise<Feed> => {
|
||||||
const fullName = `${feedData.name}@${node.domain}`
|
const fullName = `${feedData.name}@${node.domain}`
|
||||||
await elastic.create<Feed>({
|
await elastic.create<Feed>({
|
||||||
index: feedIndex,
|
index: feedIndex,
|
||||||
|
@ -25,8 +30,8 @@ export const createFeed = async (elastic: ElasticClient, feedData: StorageFeedDa
|
||||||
displayName: feedData.displayName,
|
displayName: feedData.displayName,
|
||||||
locked: feedData.locked,
|
locked: feedData.locked,
|
||||||
createdAt: feedData.createdAt.getTime(),
|
createdAt: feedData.createdAt.getTime(),
|
||||||
foundAt: (new Date()).getTime(),
|
foundAt: new Date().getTime(),
|
||||||
fields: feedData.fields.map(field => {
|
fields: feedData.fields.map((field) => {
|
||||||
return { name: field.name, value: field.value }
|
return { name: field.name, value: field.value }
|
||||||
}),
|
}),
|
||||||
extractedEmails: feedData.extractedEmails,
|
extractedEmails: feedData.extractedEmails,
|
||||||
|
@ -36,6 +41,12 @@ export const createFeed = async (elastic: ElasticClient, feedData: StorageFeedDa
|
||||||
type: feedData.type
|
type: feedData.type
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.info('Created new feed', { feedName: feedData.name, nodeDomain: node.domain })
|
console.info('Created new feed', {
|
||||||
return getFeed(elastic, fullName)
|
feedName: feedData.name,
|
||||||
|
nodeDomain: node.domain
|
||||||
|
})
|
||||||
|
return assertDefined(
|
||||||
|
await getFeed(elastic, fullName),
|
||||||
|
'Missing feed after creating it'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import { ElasticClient } from '../ElasticClient'
|
import { ElasticClient } from '../ElasticClient'
|
||||||
import feedIndex from '../Definitions/feedIndex'
|
import feedIndex from '../Definitions/feedIndex'
|
||||||
|
|
||||||
export const deleteDomainFeeds = async (elastic: ElasticClient, domains:string[]): Promise<number> => {
|
export const deleteDomainFeeds = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
domains: string[]
|
||||||
|
): Promise<number> => {
|
||||||
await elastic.indices.refresh({ index: feedIndex })
|
await elastic.indices.refresh({ index: feedIndex })
|
||||||
const result = await elastic.deleteByQuery({
|
const result = await elastic.deleteByQuery({
|
||||||
index: feedIndex,
|
index: feedIndex,
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
should: domains.map(domain => {
|
should: domains.map((domain) => {
|
||||||
return {
|
return {
|
||||||
regexp: {
|
regexp: {
|
||||||
domain: {
|
domain: {
|
||||||
|
@ -22,7 +25,8 @@ export const deleteDomainFeeds = async (elastic: ElasticClient, domains:string[]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.info('Deleted domain feeds', {
|
console.info('Deleted domain feeds', {
|
||||||
count: result.deleted, domains
|
count: result.deleted ?? 0,
|
||||||
|
domains
|
||||||
})
|
})
|
||||||
return result.deleted
|
return result.deleted ?? 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ import { ElasticClient } from '../ElasticClient'
|
||||||
import Node from '../Definitions/Node'
|
import Node from '../Definitions/Node'
|
||||||
import feedIndex from '../Definitions/feedIndex'
|
import feedIndex from '../Definitions/feedIndex'
|
||||||
|
|
||||||
export const deleteOldFeeds = async (elastic: ElasticClient, node: Node): Promise<number> => {
|
export const deleteOldFeeds = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
node: Node
|
||||||
|
): Promise<number> => {
|
||||||
await elastic.indices.refresh({ index: feedIndex })
|
await elastic.indices.refresh({ index: feedIndex })
|
||||||
const result = await elastic.deleteByQuery({
|
const result = await elastic.deleteByQuery({
|
||||||
index: feedIndex,
|
index: feedIndex,
|
||||||
|
@ -16,7 +19,9 @@ export const deleteOldFeeds = async (elastic: ElasticClient, node: Node): Promis
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.info('Deleted old feeds', {
|
console.info('Deleted old feeds', {
|
||||||
count: result.deleted, olderThen: node.refreshAttemptedAt, nodeDomain: node.domain
|
count: result.deleted ?? 0,
|
||||||
|
olderThen: node.refreshAttemptedAt,
|
||||||
|
nodeDomain: node.domain
|
||||||
})
|
})
|
||||||
return result.deleted
|
return result.deleted ?? 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ import Feed from '../Definitions/Feed'
|
||||||
import { ElasticClient } from '../ElasticClient'
|
import { ElasticClient } from '../ElasticClient'
|
||||||
import feedIndex from '../Definitions/feedIndex'
|
import feedIndex from '../Definitions/feedIndex'
|
||||||
|
|
||||||
const getFeed = async (elastic: ElasticClient, feedFullName:string):Promise<Feed> => {
|
const getFeed = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
feedFullName: string
|
||||||
|
): Promise<Feed | undefined> => {
|
||||||
const result = await elastic.get<Feed>({
|
const result = await elastic.get<Feed>({
|
||||||
index: feedIndex,
|
index: feedIndex,
|
||||||
id: feedFullName
|
id: feedFullName
|
||||||
|
|
|
@ -3,8 +3,13 @@ import Feed from '../Definitions/Feed'
|
||||||
import { ElasticClient } from '../ElasticClient'
|
import { ElasticClient } from '../ElasticClient'
|
||||||
import feedIndex from '../Definitions/feedIndex'
|
import feedIndex from '../Definitions/feedIndex'
|
||||||
import getFeed from './getFeed'
|
import getFeed from './getFeed'
|
||||||
|
import assertDefined from '../assertDefined'
|
||||||
|
|
||||||
export const updateFeed = async (elastic: ElasticClient, feed:Feed, feedData:StorageFeedData):Promise<Feed> => {
|
export const updateFeed = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
feed: Feed,
|
||||||
|
feedData: StorageFeedData
|
||||||
|
): Promise<Feed> => {
|
||||||
await elastic.update<Feed>({
|
await elastic.update<Feed>({
|
||||||
index: feedIndex,
|
index: feedIndex,
|
||||||
id: feed.fullName,
|
id: feed.fullName,
|
||||||
|
@ -20,9 +25,9 @@ export const updateFeed = async (elastic: ElasticClient, feed:Feed, feedData:Sto
|
||||||
displayName: feedData.displayName,
|
displayName: feedData.displayName,
|
||||||
locked: feedData.locked,
|
locked: feedData.locked,
|
||||||
createdAt: feedData.createdAt,
|
createdAt: feedData.createdAt,
|
||||||
refreshedAt: (new Date()).getTime(),
|
refreshedAt: new Date().getTime(),
|
||||||
type: feedData.type,
|
type: feedData.type,
|
||||||
fields: feedData.fields.map(field => {
|
fields: feedData.fields.map((field) => {
|
||||||
return { name: field.name, value: field.value }
|
return { name: field.name, value: field.value }
|
||||||
}),
|
}),
|
||||||
extractedEmails: feedData.extractedEmails,
|
extractedEmails: feedData.extractedEmails,
|
||||||
|
@ -30,5 +35,8 @@ export const updateFeed = async (elastic: ElasticClient, feed:Feed, feedData:Sto
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.info('Updated feed', { feedName: feed.name, nodeDomain: feed.domain })
|
console.info('Updated feed', { feedName: feed.name, nodeDomain: feed.domain })
|
||||||
return getFeed(elastic, feed.fullName)
|
return assertDefined(
|
||||||
|
await getFeed(elastic, feed.fullName),
|
||||||
|
'Missing updated feed'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
import dateProperty from '../Properties/dateProperty'
|
import dateProperty from '../Properties/dateProperty'
|
||||||
|
|
||||||
const assertNodeIndex = async (elastic: ElasticClient):Promise<void> => {
|
const assertNodeIndex = async (elastic: ElasticClient): Promise<void> => {
|
||||||
console.info('Setting node pipeline')
|
console.info('Setting node pipeline')
|
||||||
await elastic.ingest.putPipeline({
|
await elastic.ingest.putPipeline({
|
||||||
id: 'node',
|
id: 'node',
|
||||||
description: 'Default node pipeline',
|
description: 'Default node pipeline',
|
||||||
processors: [
|
processors: [
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
geoip: {
|
geoip: {
|
||||||
ignore_missing: true,
|
ignore_missing: true,
|
||||||
field: 'serverIps',
|
field: 'serverIps',
|
||||||
|
|
|
@ -1,24 +1,32 @@
|
||||||
import { ElasticClient } from '../ElasticClient'
|
import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
export const createMissingNodes = async (elastic: ElasticClient, domains:string[], discoveredByDomain:string|undefined):Promise<number> => {
|
export const createMissingNodes = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
domains: string[],
|
||||||
|
discoveredByDomain: string | undefined
|
||||||
|
): Promise<number> => {
|
||||||
const response = await elastic.bulk({
|
const response = await elastic.bulk({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
body: domains.flatMap(domain => [
|
body: domains.flatMap((domain) => [
|
||||||
{
|
{
|
||||||
create: { _id: domain }
|
create: { _id: domain }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: domain,
|
domain,
|
||||||
discoveredByDomain,
|
discoveredByDomain,
|
||||||
foundAt: (new Date()).getTime()
|
foundAt: new Date().getTime()
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
const createdCount = response.items.filter(item => item.create.status === 201).length
|
const createdCount = response.items.filter(
|
||||||
|
(item) => item.create?.status === 201
|
||||||
|
).length
|
||||||
console.warn('Created new nodes', {
|
console.warn('Created new nodes', {
|
||||||
requestedCount: domains.length,
|
requestedCount: domains.length,
|
||||||
createdCount: createdCount,
|
createdCount,
|
||||||
errors: response.items.filter(item => item.create.status !== 201).map(item => item.create.error.reason)
|
errors: response.items
|
||||||
|
.filter((item) => item.create?.status !== 201)
|
||||||
|
.map((item) => item.create?.error?.reason)
|
||||||
})
|
})
|
||||||
return createdCount
|
return createdCount
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import { ElasticClient } from '../ElasticClient'
|
import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
|
|
||||||
export const deleteDomainNodes = async (elastic: ElasticClient, domains:string[]): Promise<number> => {
|
export const deleteDomainNodes = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
domains: string[]
|
||||||
|
): Promise<number> => {
|
||||||
await elastic.indices.refresh({ index: nodeIndex })
|
await elastic.indices.refresh({ index: nodeIndex })
|
||||||
const result = await elastic.deleteByQuery({
|
const result = await elastic.deleteByQuery({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
should: domains.map(domain => {
|
should: domains.map((domain) => {
|
||||||
return {
|
return {
|
||||||
regexp: {
|
regexp: {
|
||||||
domain: {
|
domain: {
|
||||||
|
@ -22,7 +25,8 @@ export const deleteDomainNodes = async (elastic: ElasticClient, domains:string[]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.info('Deleted domain nodes', {
|
console.info('Deleted domain nodes', {
|
||||||
count: result.deleted, domains
|
count: result.deleted ?? 0,
|
||||||
|
domains
|
||||||
})
|
})
|
||||||
return result.deleted
|
return result.deleted ?? 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import Node from '../Definitions/Node'
|
||||||
import { ElasticClient } from '../ElasticClient'
|
import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
|
|
||||||
export const fetchNodeToProcess = async (elastic: ElasticClient): Promise<Node> => {
|
export const fetchNodeToProcess = async (
|
||||||
|
elastic: ElasticClient
|
||||||
|
): Promise<Node> => {
|
||||||
await elastic.indices.refresh({ index: nodeIndex })
|
await elastic.indices.refresh({ index: nodeIndex })
|
||||||
let node = await findNotProcessedNodeWithAttemptLimit(elastic)
|
let node = await findNotProcessedNodeWithAttemptLimit(elastic)
|
||||||
if (node !== null) {
|
if (node !== null) {
|
||||||
|
|
|
@ -2,42 +2,55 @@ import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
import Node from '../Definitions/Node'
|
import Node from '../Definitions/Node'
|
||||||
|
|
||||||
const findNodeWithOldestRefreshWithLimits = async (elastic: ElasticClient): Promise<Node | null> => {
|
const findNodeWithOldestRefreshWithLimits = async (
|
||||||
|
elastic: ElasticClient
|
||||||
|
): Promise<Node | null> => {
|
||||||
const currentTimestamp = Date.now()
|
const currentTimestamp = Date.now()
|
||||||
const attemptLimitMilliseconds = parseInt(process.env.REATTEMPT_MINUTES ?? '60') * 60 * 1000
|
const attemptLimitMilliseconds =
|
||||||
|
parseInt(process.env.REATTEMPT_MINUTES ?? '60') * 60 * 1000
|
||||||
const attemptLimitDate = new Date(currentTimestamp - attemptLimitMilliseconds)
|
const attemptLimitDate = new Date(currentTimestamp - attemptLimitMilliseconds)
|
||||||
const refreshLimitMilliseconds = parseInt(process.env.REFRESH_HOURS ?? '168') * 60 * 60 * 1000
|
const refreshLimitMilliseconds =
|
||||||
|
parseInt(process.env.REFRESH_HOURS ?? '168') * 60 * 60 * 1000
|
||||||
const refreshLimitDate = new Date(currentTimestamp - refreshLimitMilliseconds)
|
const refreshLimitDate = new Date(currentTimestamp - refreshLimitMilliseconds)
|
||||||
console.log('Searching instance not refreshed for longest time and before refreshLimit and attemptLimit', {
|
console.log(
|
||||||
refreshLimitMilliseconds,
|
'Searching instance not refreshed for longest time and before refreshLimit and attemptLimit',
|
||||||
refreshLimitDate,
|
{
|
||||||
attemptLimitDate,
|
refreshLimitMilliseconds,
|
||||||
attemptLimitMilliseconds
|
refreshLimitDate,
|
||||||
})
|
attemptLimitDate,
|
||||||
|
attemptLimitMilliseconds
|
||||||
|
}
|
||||||
|
)
|
||||||
const result = await elastic.search<Node>({
|
const result = await elastic.search<Node>({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
body: {
|
body: {
|
||||||
size: 1,
|
size: 1,
|
||||||
sort: [{
|
sort: [
|
||||||
refreshedAt: { order: 'asc' }
|
{
|
||||||
}],
|
refreshedAt: { order: 'asc' }
|
||||||
|
}
|
||||||
|
],
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
must: [
|
must: [
|
||||||
{ range: { refreshedAt: { lt: refreshLimitDate.getTime() } } }
|
{ range: { refreshedAt: { lt: refreshLimitDate.getTime() } } }
|
||||||
],
|
],
|
||||||
should: [
|
should: [
|
||||||
{ range: { refreshAttemptedAt: { lt: attemptLimitDate.getTime() } } },
|
{
|
||||||
{ bool: { must_not: [{ exists: { field: 'refreshAttemptedAt' } }] } }
|
range: { refreshAttemptedAt: { lt: attemptLimitDate.getTime() } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bool: { must_not: [{ exists: { field: 'refreshAttemptedAt' } }] }
|
||||||
|
}
|
||||||
],
|
],
|
||||||
minimum_should_match: 1
|
minimum_should_match: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (result.hits.hits.length > 0) {
|
const node = result.hits.hits.pop()?._source
|
||||||
const node = result.hits.hits[0]._source
|
if (node !== undefined) {
|
||||||
console.log('Found oldest node', { node })
|
console.info('Found oldest node', { node })
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -2,33 +2,44 @@ import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
import Node from '../Definitions/Node'
|
import Node from '../Definitions/Node'
|
||||||
|
|
||||||
const findNotProcessedNodeWithAttemptLimit = async (elastic: ElasticClient): Promise<Node|null> => {
|
const findNotProcessedNodeWithAttemptLimit = async (
|
||||||
|
elastic: ElasticClient
|
||||||
|
): Promise<Node | null> => {
|
||||||
const currentTimestamp = Date.now()
|
const currentTimestamp = Date.now()
|
||||||
const attemptLimitMilliseconds = parseInt(process.env.REATTEMPT_MINUTES ?? '60') * 60 * 1000
|
const attemptLimitMilliseconds =
|
||||||
|
parseInt(process.env.REATTEMPT_MINUTES ?? '60') * 60 * 1000
|
||||||
const attemptLimitDate = new Date(currentTimestamp - attemptLimitMilliseconds)
|
const attemptLimitDate = new Date(currentTimestamp - attemptLimitMilliseconds)
|
||||||
console.log('Searching for not yet processed node not attempted before attemptLimit', { attemptLimitDate, attemptLimitMilliseconds })
|
console.log(
|
||||||
|
'Searching for not yet processed node not attempted before attemptLimit',
|
||||||
|
{ attemptLimitDate, attemptLimitMilliseconds }
|
||||||
|
)
|
||||||
const result = await elastic.search<Node>({
|
const result = await elastic.search<Node>({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
body: {
|
body: {
|
||||||
size: 1,
|
size: 1,
|
||||||
sort: [{
|
sort: [
|
||||||
foundAt: { order: 'asc' }
|
{
|
||||||
}],
|
foundAt: { order: 'asc' }
|
||||||
|
}
|
||||||
|
],
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
must_not: [
|
must_not: [{ exists: { field: 'refreshedAt' } }],
|
||||||
{ exists: { field: 'refreshedAt' } }],
|
|
||||||
should: [
|
should: [
|
||||||
{ bool: { must_not: [{ exists: { field: 'refreshAttemptedAt' } }] } },
|
{
|
||||||
{ range: { refreshAttemptedAt: { lt: attemptLimitDate.getTime() } } }
|
bool: { must_not: [{ exists: { field: 'refreshAttemptedAt' } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: { refreshAttemptedAt: { lt: attemptLimitDate.getTime() } }
|
||||||
|
}
|
||||||
],
|
],
|
||||||
minimum_should_match: 1
|
minimum_should_match: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (result.hits.hits.length > 0) {
|
const node = result.hits.hits.pop()?._source
|
||||||
const node = result.hits.hits[0]._source
|
if (node !== undefined) {
|
||||||
console.log('Found not yet processed node', { node })
|
console.log('Found not yet processed node', { node })
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ import { ElasticClient } from '../ElasticClient'
|
||||||
import Node from '../Definitions/Node'
|
import Node from '../Definitions/Node'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
|
|
||||||
const getNode = async (elastic:ElasticClient, domain:string):Promise<Node> => {
|
const getNode = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
domain: string
|
||||||
|
): Promise<Node | undefined> => {
|
||||||
const result = await elastic.get<Node>({
|
const result = await elastic.get<Node>({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
id: domain
|
id: domain
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import getBannedDomains from '../../Jobs/Seed/getBannedDomains'
|
import getBannedDomains from '../../Jobs/Seed/getBannedDomains'
|
||||||
|
|
||||||
export default function isDomainNotBanned (domain):boolean {
|
export default function isDomainNotBanned (domain): boolean {
|
||||||
return getBannedDomains().filter(
|
return (
|
||||||
banned => domain.match(new RegExp('(.*\\.)?' + banned, 'gi')) !== null
|
getBannedDomains().filter(
|
||||||
).length === 0
|
(banned) => domain.match(new RegExp('(.*\\.)?' + banned, 'gi')) !== null
|
||||||
|
).length === 0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,14 @@ import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
import Node from '../Definitions/Node'
|
import Node from '../Definitions/Node'
|
||||||
import getNode from './getNode'
|
import getNode from './getNode'
|
||||||
|
import assertDefined from '../assertDefined'
|
||||||
|
|
||||||
export const setNodeRefreshAttempted = async (elastic: ElasticClient, node:Node):Promise<Node> => {
|
export const setNodeRefreshAttempted = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
node: Node
|
||||||
|
): Promise<Node> => {
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
console.info('Setting node refresh attempt', { domain: node.domain, date: date })
|
console.info('Setting node refresh attempt', { domain: node.domain, date })
|
||||||
await elastic.update<Node>({
|
await elastic.update<Node>({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
id: node.domain,
|
id: node.domain,
|
||||||
|
@ -13,5 +17,8 @@ export const setNodeRefreshAttempted = async (elastic: ElasticClient, node:Node)
|
||||||
refreshAttemptedAt: date.getTime()
|
refreshAttemptedAt: date.getTime()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return getNode(elastic, node.domain)
|
return assertDefined(
|
||||||
|
await getNode(elastic, node.domain),
|
||||||
|
'Missing node after updating it'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,14 @@ import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
import Node from '../Definitions/Node'
|
import Node from '../Definitions/Node'
|
||||||
import getNode from './getNode'
|
import getNode from './getNode'
|
||||||
|
import assertDefined from '../assertDefined'
|
||||||
|
|
||||||
export const setNodeRefreshed = async (elastic: ElasticClient, node:Node):Promise<Node> => {
|
export const setNodeRefreshed = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
node: Node
|
||||||
|
): Promise<Node> => {
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
console.info('Setting node refreshed', { domain: node.domain, date: date })
|
console.info('Setting node refreshed', { domain: node.domain, date })
|
||||||
await elastic.update<Node>({
|
await elastic.update<Node>({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
id: node.domain,
|
id: node.domain,
|
||||||
|
@ -13,5 +17,8 @@ export const setNodeRefreshed = async (elastic: ElasticClient, node:Node):Promis
|
||||||
refreshedAt: date.getTime()
|
refreshedAt: date.getTime()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return getNode(elastic, node.domain)
|
return assertDefined(
|
||||||
|
await getNode(elastic, node.domain),
|
||||||
|
'Missing node after updating it'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,13 @@ import nodeIndex from '../Definitions/nodeIndex'
|
||||||
import Node from '../Definitions/Node'
|
import Node from '../Definitions/Node'
|
||||||
import getNode from './getNode'
|
import getNode from './getNode'
|
||||||
import { NodeStats } from '../../Jobs/Nodes/updateNodeFeedStats'
|
import { NodeStats } from '../../Jobs/Nodes/updateNodeFeedStats'
|
||||||
|
import assertDefined from '../assertDefined'
|
||||||
|
|
||||||
export const setNodeStats = async (elastic: ElasticClient, node:Node, stats: NodeStats):Promise<Node> => {
|
export const setNodeStats = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
node: Node,
|
||||||
|
stats: NodeStats
|
||||||
|
): Promise<Node> => {
|
||||||
console.info('Setting node stats', { domain: node.domain, stats })
|
console.info('Setting node stats', { domain: node.domain, stats })
|
||||||
await elastic.update<Node>({
|
await elastic.update<Node>({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
|
@ -14,5 +19,8 @@ export const setNodeStats = async (elastic: ElasticClient, node:Node, stats: Nod
|
||||||
channelFeedCount: stats.channel
|
channelFeedCount: stats.channel
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return getNode(elastic, node.domain)
|
return assertDefined(
|
||||||
|
await getNode(elastic, node.domain),
|
||||||
|
'Missing node after updating it'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,20 @@ import Node from '../Definitions/Node'
|
||||||
import { ElasticClient } from '../ElasticClient'
|
import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
import getNode from './getNode'
|
import getNode from './getNode'
|
||||||
|
import assertDefined from '../assertDefined'
|
||||||
|
|
||||||
const assertPositiveInt = (number:number|undefined):number|undefined => {
|
const assertPositiveInt = (number: number | undefined): number | undefined => {
|
||||||
if (number === undefined) {
|
if (number === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return Math.max(0, Math.round(number))
|
return Math.max(0, Math.round(number))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateNodeInfo = async (elastic: ElasticClient, node: Node, nodeInfo:NodeInfo):Promise<Node> => {
|
export const updateNodeInfo = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
node: Node,
|
||||||
|
nodeInfo: NodeInfo
|
||||||
|
): Promise<Node> => {
|
||||||
await elastic.update<Node>({
|
await elastic.update<Node>({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
id: node.domain,
|
id: node.domain,
|
||||||
|
@ -20,14 +25,21 @@ export const updateNodeInfo = async (elastic: ElasticClient, node: Node, nodeInf
|
||||||
openRegistrations: nodeInfo?.openRegistrations,
|
openRegistrations: nodeInfo?.openRegistrations,
|
||||||
softwareName: nodeInfo?.software?.name?.toLocaleLowerCase(),
|
softwareName: nodeInfo?.software?.name?.toLocaleLowerCase(),
|
||||||
softwareVersion: nodeInfo?.software?.version,
|
softwareVersion: nodeInfo?.software?.version,
|
||||||
halfYearActiveUserCount: assertPositiveInt(nodeInfo?.usage?.users?.activeHalfyear),
|
halfYearActiveUserCount: assertPositiveInt(
|
||||||
monthActiveUserCount: assertPositiveInt(nodeInfo?.usage?.users.activeMonth),
|
nodeInfo?.usage?.users?.activeHalfyear
|
||||||
|
),
|
||||||
|
monthActiveUserCount: assertPositiveInt(
|
||||||
|
nodeInfo?.usage?.users?.activeMonth
|
||||||
|
),
|
||||||
statusesCount: assertPositiveInt(nodeInfo?.usage?.localPosts),
|
statusesCount: assertPositiveInt(nodeInfo?.usage?.localPosts),
|
||||||
totalUserCount: assertPositiveInt(nodeInfo?.usage?.users?.total)
|
totalUserCount: assertPositiveInt(nodeInfo?.usage?.users?.total)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const resultNode = await getNode(elastic, node.domain)
|
const resultNode = assertDefined(
|
||||||
|
await getNode(elastic, node.domain),
|
||||||
|
'Missing node after updating it'
|
||||||
|
)
|
||||||
console.info('Updated node info', { node })
|
console.info('Updated node info', { node })
|
||||||
return resultNode
|
return resultNode
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,13 @@ import Node from '../Definitions/Node'
|
||||||
import { ElasticClient } from '../ElasticClient'
|
import { ElasticClient } from '../ElasticClient'
|
||||||
import nodeIndex from '../Definitions/nodeIndex'
|
import nodeIndex from '../Definitions/nodeIndex'
|
||||||
import getNode from './getNode'
|
import getNode from './getNode'
|
||||||
|
import assertDefined from '../assertDefined'
|
||||||
|
|
||||||
export const updateNodeIps = async (elastic:ElasticClient, node: Node, ips:string[]):Promise<Node> => {
|
export const updateNodeIps = async (
|
||||||
|
elastic: ElasticClient,
|
||||||
|
node: Node,
|
||||||
|
ips: string[]
|
||||||
|
): Promise<Node> => {
|
||||||
await elastic.update<Node>({
|
await elastic.update<Node>({
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
id: node.domain,
|
id: node.domain,
|
||||||
|
@ -11,7 +16,10 @@ export const updateNodeIps = async (elastic:ElasticClient, node: Node, ips:strin
|
||||||
serverIps: ips
|
serverIps: ips
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const resultNode = await getNode(elastic, node.domain)
|
const resultNode = assertDefined(
|
||||||
|
await getNode(elastic, node.domain),
|
||||||
|
'Missing node after updating it'
|
||||||
|
)
|
||||||
console.info('Updated node ips', { resultNode })
|
console.info('Updated node ips', { resultNode })
|
||||||
return resultNode
|
return resultNode
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { MappingProperty } from '@elastic/elasticsearch/lib/api/types'
|
import { MappingProperty } from '@elastic/elasticsearch/lib/api/types'
|
||||||
|
|
||||||
const dateProperty:MappingProperty = { type: 'date', format: 'epoch_millis' }
|
const dateProperty: MappingProperty = { type: 'date', format: 'epoch_millis' }
|
||||||
|
|
||||||
export default dateProperty
|
export default dateProperty
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
export default function assertDefined<T> (
|
||||||
|
value: T | undefined | null,
|
||||||
|
errorMessage: string
|
||||||
|
): T {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
throw new Error(errorMessage)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
|
@ -1,4 +1,9 @@
|
||||||
export const extractEmails = (text:string):string[] => {
|
export const extractEmails = (text: string): string[] => {
|
||||||
return (text.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi) || [])
|
const matches = text.match(
|
||||||
.map(email => email.toLowerCase()) || []
|
/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi
|
||||||
|
)
|
||||||
|
if (matches === null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return matches.map((email) => email.toLowerCase())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
export const extractTags = (text:string):string[] => {
|
export const extractTags = (text: string): string[] => {
|
||||||
return (text.match(/#[a-z0-9_]+/gi) || [])
|
const matches = text.match(/#[a-z0-9_]+/gi)
|
||||||
.map(hashtag => hashtag.substring(1).toLowerCase()) || []
|
if (matches === null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return matches.map((hashtag) => hashtag.substring(1).toLowerCase())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,25 @@ import elasticClient from './Storage/ElasticClient'
|
||||||
import deleteDomains from './Jobs/Seed/deleteBannedNodes'
|
import deleteDomains from './Jobs/Seed/deleteBannedNodes'
|
||||||
import getBannedDomains from './Jobs/Seed/getBannedDomains'
|
import getBannedDomains from './Jobs/Seed/getBannedDomains'
|
||||||
|
|
||||||
|
const timeout = async (ms: number): Promise<void> => {
|
||||||
|
return await new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
||||||
const loop = async (): Promise<void> => {
|
const loop = async (): Promise<void> => {
|
||||||
|
// noinspection InfiniteLoopJS
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
await processNextNode(elasticClient, providerRegistry)
|
await processNextNode(elasticClient, providerRegistry)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(err)
|
console.warn(err)
|
||||||
const waitForJobMilliseconds = parseInt(process.env.WAIT_FOR_JOB_MINUTES ?? '60') * 60 * 1000
|
const waitForJobMilliseconds =
|
||||||
console.info('Delaying next node process', { timeoutMilliseconds: waitForJobMilliseconds, timeoutDate: new Date(Date.now() + waitForJobMilliseconds), now: new Date() })
|
parseInt(process.env.WAIT_FOR_JOB_MINUTES ?? '60') * 60 * 1000
|
||||||
setTimeout(loop, waitForJobMilliseconds)
|
console.info('Delaying next node process', {
|
||||||
return
|
timeoutMilliseconds: waitForJobMilliseconds,
|
||||||
|
timeoutDate: new Date(Date.now() + waitForJobMilliseconds),
|
||||||
|
now: new Date()
|
||||||
|
})
|
||||||
|
await timeout(waitForJobMilliseconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,9 +34,13 @@ const app = async (): Promise<void> => {
|
||||||
await assertNodeIndex(elasticClient)
|
await assertNodeIndex(elasticClient)
|
||||||
await assertFeedIndex(elasticClient)
|
await assertFeedIndex(elasticClient)
|
||||||
await deleteDomains(elasticClient, getBannedDomains())
|
await deleteDomains(elasticClient, getBannedDomains())
|
||||||
const seedDomains = (process.env.SEED_NODE_DOMAIN ?? 'mastodon.social').split(',')
|
const seedDomains = (process.env.SEED_NODE_DOMAIN ?? 'mastodon.social').split(
|
||||||
|
','
|
||||||
|
)
|
||||||
await addNodeSeed(elasticClient, seedDomains)
|
await addNodeSeed(elasticClient, seedDomains)
|
||||||
setTimeout(loop)
|
await loop()
|
||||||
}
|
}
|
||||||
|
|
||||||
app()
|
app()
|
||||||
|
.then(() => console.info('App finished'))
|
||||||
|
.catch((error) => console.error('App was interrupted', { error }))
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
"outDir": "./dist/",
|
"outDir": "./dist/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
},
|
},
|
||||||
"files": ["./src/app.ts"]
|
"files": ["./src/app.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"defaultSeverity": "error",
|
|
||||||
"extends": [
|
|
||||||
"tslint:recommended"
|
|
||||||
],
|
|
||||||
"jsRules": {},
|
|
||||||
"rules": {
|
|
||||||
"no-console": false
|
|
||||||
},
|
|
||||||
"rulesDirectory": []
|
|
||||||
}
|
|
Plik diff jest za duży
Load Diff
Ładowanie…
Reference in New Issue