commit bfaab359eb31c6da4679576c2b7d904c25606fdd Author: ale Date: Fri Jul 18 22:19:35 2025 +0200 initial commit Signed-off-by: ale diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..8a8e013 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,35 @@ +kind: pipeline +name: code-build-linux-amd64 +type: docker + +platform: + os: linux + arch: arm64 + +steps: +- name: build + image: docker:dind + privileged: true + environment: + USER: + from_secret: user + PASS: + from_secret: pass + REGISTRY: + from_secret: registry + volumes: + - name: dockersock + path: /var/run/docker.sock + commands: + - docker login -u $USER -p $PASS $REGISTRY + - docker buildx build -t $REGISTRY/fediblock-instance . + - docker push $REGISTRY/fediblock-instance + when: + event: + - push + - tag + +volumes: +- name: dockersock + host: + path: /var/run/docker.sock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d893bfc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**/node_modules/ +**/*.lock +**/*-lock.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c066b43 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM node:22-slim +RUN apt update && apt install -y git && apt clean +COPY --chown=node:node . /fediblock-instance +USER node +RUN yarn config set network-timeout 300000 +WORKDIR /fediblock-instance/front +RUN yarn +WORKDIR /fediblock-instance/back +RUN yarn && yarn build +EXPOSE 4000 +ENTRYPOINT ["yarn", "start"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e43b6ad --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Fediblock Instance + +## Another instance to search public blocks and do stats and more, like one FBA but with NodeJS powers. + +## We need one mongodb instance for federation and one elasticsearch node to storage data. + +## Install + +``` +$ yarn or npm i +``` + +## Build frontend + +``` +$ yarn build or npm run build +``` + +## Configuration + +- edit `back/constant.js` with your environment preferences + +## Start + +``` +$ yarn start or npm run start +``` + +## Docker + +``` +$ docker compose build or docker pull registry.manalejandro.com/fediblock-instance +$ docker compose pull +$ docker compose up -d +``` + +### Best deploy with subdomain in one HTTPS proxy like nginx or apache throught 4000/tcp port + + +## License + +MIT \ No newline at end of file diff --git a/back/constant.js b/back/constant.js new file mode 100644 index 0000000..39b2408 --- /dev/null +++ b/back/constant.js @@ -0,0 +1,17 @@ +module.exports = { + index: 'fediblock', // elasticsearch index name + nick: 'fediblockbot', // nick of bot + icon: 'https://manalejandro.com/favicon.png', // icon of the instance + dburl: 'mongodb://fediblock-mongodb:27017', // mongodb connection + dbname: 'fediblock', // mongodb database name + apexdomain: 'fediblock.manalejandro.com', // domain of the instance + agent: 'fediblock.manalejandro.com', // agent of fetch requests + elasticnode: 'http://fediblock-elasticsearch:9200', // elasticsearch connection + workers: 3, // async concurrent workers to scan + threads: 2, // threads of each worker + schedule: '5,11,17,23', // UTC hours to publish bot followers federated stats + taskdeletedup: '6', // task hour to delete dups index instances entries + filterdomains: ['activitypub-troll.cf'], // domains filtered to scan + initialscan: 'mastodon.social', // initial federated domain to scan + abort_timeout: 5000 // Timeout of abort scan request +} diff --git a/back/fediblock-mapping.json b/back/fediblock-mapping.json new file mode 100644 index 0000000..6621f16 --- /dev/null +++ b/back/fediblock-mapping.json @@ -0,0 +1,7191 @@ +{ + "mappings": { + "properties": { + "api": { + "properties": { + "accent_color": { + "type": "text" + }, + "account_domain": { + "type": "text" + }, + "allow_unauthenticated": { + "properties": { + "timeline_local": { + "type": "boolean" + } + } + }, + "altids": { + "type": "object", + "enabled": false + }, + "api_schema": { + "type": "text" + }, + "approval_required": { + "type": "boolean" + }, + "asana-client-id": { + "type": "text" + }, + "atlassian-api-key": { + "type": "text" + }, + "authentication_methods": { + "type": "text" + }, + "avatar_upload_limit": { + "type": "long" + }, + "aws-access-secret": { + "type": "text" + }, + "background_image": { + "type": "text" + }, + "background_upload_limit": { + "type": "long" + }, + "banner_upload_limit": { + "type": "long" + }, + "bitbucket-client-id": { + "type": "text" + }, + "bitbucket-client-key": { + "type": "text" + }, + "blockchains": { + "properties": { + "chain_id": { + "type": "text" + }, + "features": { + "properties": { + "gate": { + "type": "boolean" + }, + "minter": { + "type": "boolean" + }, + "subscriptions": { + "type": "boolean" + } + } + } + } + }, + "buffer": { + "type": "long" + }, + "chat_limit": { + "type": "long" + }, + "command": { + "type": "text" + }, + "compile": { + "type": "text" + }, + "config": { + "properties": { + "dropbox-service-secret": { + "type": "text" + }, + "environments": { + "properties": { + "datadog-access-key": { + "type": "text" + }, + "dev": { + "properties": { + "aws-access-key": { + "type": "text" + }, + "flickr-access-token": { + "type": "text" + }, + "mapbox-api-token": { + "type": "text" + }, + "phases": { + "properties": { + "arrivalRate": { + "type": "long" + }, + "duration": { + "type": "long" + } + } + }, + "sidekiq-key": { + "type": "text" + }, + "target": { + "type": "text" + } + } + }, + "qa": { + "properties": { + "bittrex-token-key": { + "type": "text" + }, + "confluent-access-secret": { + "type": "text" + }, + "easypost-test-api-secret": { + "type": "text" + }, + "phases": { + "properties": { + "arrivalRate": { + "type": "long" + }, + "duration": { + "type": "long" + } + } + }, + "target": { + "type": "text" + }, + "twitter-api-token": { + "type": "text" + } + } + }, + "shopify-private-app-access-token": { + "type": "text" + }, + "slack-app-secret": { + "type": "text" + }, + "travisci-access-token": { + "type": "text" + } + } + }, + "frameio-service-key": { + "type": "text" + }, + "http": { + "properties": { + "freshbooks-access-key": { + "type": "text" + }, + "kucoin-secret-token": { + "type": "text" + }, + "linear-client-token": { + "type": "text" + }, + "npm-access-token": { + "type": "text" + }, + "timeout": { + "type": "long" + } + } + }, + "phases": { + "properties": { + "arrivalRate": { + "type": "long" + }, + "duration": { + "type": "long" + }, + "name": { + "type": "text" + } + } + }, + "plugins": { + "properties": { + "discord-client-secret": { + "type": "text" + }, + "expect": { + "properties": { + "bitbucket-client-id": { + "type": "text" + }, + "hashicorp-tf-api-secret": { + "type": "text" + }, + "outputFormat": { + "type": "text" + }, + "shopify-private-app-access-key": { + "type": "text" + }, + "snyk-service-secret": { + "type": "text" + } + } + }, + "metrics-by-endpoint": { + "properties": { + "doppler-service-secret": { + "type": "text" + }, + "hubspot-service-token": { + "type": "text" + }, + "linear-client-key": { + "type": "text" + }, + "shopify-access-key": { + "type": "text" + }, + "useOnlyRequestNames": { + "type": "boolean" + } + } + }, + "openai-service-token": { + "type": "text" + }, + "publish-metrics": { + "properties": { + "event": { + "properties": { + "priority": { + "type": "text" + }, + "send": { + "type": "boolean" + }, + "status": { + "type": "text" + }, + "tags": { + "type": "text" + }, + "title": { + "type": "text" + } + } + }, + "host": { + "type": "text" + }, + "port": { + "type": "text" + }, + "prefix": { + "type": "text" + }, + "tags": { + "type": "text" + }, + "type": { + "type": "text" + } + } + }, + "slack-config-refresh-secret": { + "type": "text" + }, + "sumologic-access-key": { + "type": "text" + } + } + }, + "processor": { + "type": "text" + }, + "sentry-access-key": { + "type": "text" + }, + "target": { + "type": "text" + }, + "tls": { + "properties": { + "adafruit-service-key": { + "type": "text" + }, + "dropbox-short-lived-service-secret": { + "type": "text" + }, + "openai-api-secret": { + "type": "text" + }, + "rejectUnauthorized": { + "type": "boolean" + }, + "twitter-api-secret": { + "type": "text" + } + } + }, + "twilio-api-token": { + "type": "text" + } + } + }, + "configuration": { + "properties": { + "accounts": { + "properties": { + "allow_custom_css": { + "type": "boolean" + }, + "characters_reserved_per_emoji": { + "type": "long" + }, + "max_display_name": { + "type": "long" + }, + "max_favourite_tags": { + "type": "long" + }, + "max_featured_tags": { + "type": "long" + }, + "max_profile_fields": { + "type": "long" + }, + "max_status_pins": { + "type": "long" + } + } + }, + "contact_account": { + "type": "text" + }, + "emoji_reactions": { + "properties": { + "max_reactions": { + "type": "long" + }, + "max_reactions_per_account": { + "type": "long" + }, + "max_reactions_per_remote_account": { + "type": "long" + } + } + }, + "emojis": { + "properties": { + "emoji_size_limit": { + "type": "long" + } + } + }, + "media_attachment": { + "properties": { + "image_size_limit": { + "type": "long" + }, + "supported_mime_types": { + "type": "text" + }, + "video_size_limit": { + "type": "long" + } + } + }, + "media_attachments": { + "properties": { + "attachments_limit": { + "type": "long" + }, + "image_matrix_limit": { + "type": "long" + }, + "image_size_limit": { + "type": "long" + }, + "max_characters": { + "type": "long" + }, + "polls": { + "properties": { + "max_characters_per_option": { + "type": "long" + }, + "max_expiration": { + "type": "long" + }, + "max_options": { + "type": "long" + }, + "min_expiration": { + "type": "long" + } + } + }, + "supported_mime_types": { + "type": "text" + }, + "video_frame_limit": { + "type": "long" + }, + "video_frame_rate_limit": { + "type": "long" + }, + "video_matrix_limit": { + "type": "long" + }, + "video_size_limit": { + "type": "long" + } + } + }, + "oidc_enabled": { + "type": "boolean" + }, + "polls": { + "properties": { + "allow_image": { + "type": "boolean" + }, + "allow_media": { + "type": "boolean" + }, + "max_characters_per_option": { + "type": "long" + }, + "max_expiration": { + "type": "double" + }, + "max_options": { + "type": "long" + }, + "min_expiration": { + "type": "double" + }, + "min_options": { + "type": "long" + } + } + }, + "reaction_deck": { + "properties": { + "max_emojis": { + "type": "long" + } + } + }, + "reactions": { + "properties": { + "default_reaction": { + "type": "text" + }, + "max_reactions": { + "type": "long" + }, + "max_reactions_per_account": { + "type": "long" + } + } + }, + "rules": { + "properties": { + "id": { + "type": "long" + }, + "text": { + "type": "text" + } + } + }, + "search": { + "properties": { + "enabled": { + "type": "boolean" + }, + "supported_operator": { + "type": "text" + }, + "supported_order": { + "type": "text" + }, + "supported_prefix": { + "type": "text" + }, + "supported_properties": { + "type": "text" + }, + "supported_searchablity_filter": { + "type": "text" + } + } + }, + "status_references": { + "properties": { + "max_references": { + "type": "long" + } + } + }, + "statuses": { + "properties": { + "characters_reserved_per_url": { + "type": "long" + }, + "cw_max_characters": { + "type": "long" + }, + "max_characters": { + "type": "long" + }, + "max_expiration": { + "type": "double" + }, + "max_media_attachments": { + "type": "long" + }, + "max_media_attachments_from_activitypub": { + "type": "long" + }, + "max_media_attachments_with_poll": { + "type": "long" + }, + "min_expiration": { + "type": "double" + }, + "supported_expires_actions": { + "type": "text" + }, + "supported_mime_types": { + "type": "text" + }, + "supported_toggles": { + "properties": { + "local_only": { + "type": "boolean" + }, + "sensitive": { + "type": "boolean" + } + } + }, + "supported_visibility": { + "properties": { + "direct": { + "type": "boolean" + }, + "private": { + "type": "boolean" + }, + "public": { + "type": "boolean" + }, + "unlisted": { + "type": "boolean" + } + } + } + } + }, + "translation": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "urls": { + "type": "object", + "enabled": false + } + } + }, + "contact_account": { + "properties": { + "acct": { + "type": "keyword" + }, + "actor_id": { + "type": "text" + }, + "all_emojis": { + "properties": { + "shortcode": { + "type": "text" + }, + "static_url": { + "type": "text" + }, + "url": { + "type": "text" + }, + "visible_in_picker": { + "type": "boolean" + } + } + }, + "avatar": { + "type": "text" + }, + "avatar_description": { + "type": "text" + }, + "avatar_frame_type": { + "type": "text" + }, + "avatar_media_id": { + "type": "text" + }, + "avatar_static": { + "type": "text" + }, + "avatar_thumbhash": { + "type": "text" + }, + "bot": { + "type": "boolean" + }, + "cat": { + "type": "boolean" + }, + "created_at": { + "type": "date" + }, + "custom_css": { + "type": "text" + }, + "discoverable": { + "type": "boolean" + }, + "display_name": { + "type": "text" + }, + "emoji_reaction_available_server": { + "type": "boolean" + }, + "emojis": { + "properties": { + "aliases": { + "type": "text" + }, + "category": { + "type": "text" + }, + "height": { + "type": "long" + }, + "is_sensitive": { + "type": "boolean" + }, + "sensitive": { + "type": "boolean" + }, + "shortcode": { + "type": "text" + }, + "static_url": { + "type": "text" + }, + "thumbhash": { + "type": "text" + }, + "url": { + "type": "text" + }, + "visible_in_picker": { + "type": "boolean" + }, + "width": { + "type": "long" + } + } + }, + "enable_rss": { + "type": "boolean" + }, + "fields": { + "properties": { + "is_legacy_proof": { + "type": "boolean" + }, + "name": { + "type": "text" + }, + "value": { + "type": "text" + }, + "verified_at": { + "type": "date" + } + } + }, + "followers_count": { + "type": "long" + }, + "following_count": { + "type": "long" + }, + "fqn": { + "type": "text" + }, + "group": { + "type": "boolean" + }, + "header": { + "type": "text" + }, + "header_description": { + "type": "text" + }, + "header_media_id": { + "type": "text" + }, + "header_static": { + "type": "text" + }, + "header_thumbhash": { + "type": "text" + }, + "hide_boosts": { + "type": "boolean" + }, + "hide_collections": { + "type": "boolean" + }, + "id": { + "type": "text" + }, + "identity_proofs": { + "properties": { + "is_legacy_proof": { + "type": "boolean" + }, + "name": { + "type": "text" + }, + "value": { + "type": "text" + }, + "verified_at": { + "type": "date" + } + } + }, + "indexable": { + "type": "boolean" + }, + "is_admin": { + "type": "boolean" + }, + "jam_identity": { + "type": "text" + }, + "last_status_at": { + "type": "date" + }, + "liker_id": { + "type": "text" + }, + "limited": { + "type": "boolean" + }, + "local": { + "type": "boolean" + }, + "locked": { + "type": "boolean" + }, + "memorial": { + "type": "boolean" + }, + "mention_policy": { + "type": "text" + }, + "moved": { + "properties": { + "acct": { + "type": "text" + }, + "avatar": { + "type": "text" + }, + "avatar_static": { + "type": "text" + }, + "bot": { + "type": "boolean" + }, + "created_at": { + "type": "date" + }, + "discoverable": { + "type": "boolean" + }, + "display_name": { + "type": "text" + }, + "emojis": { + "properties": { + "shortcode": { + "type": "text" + }, + "static_url": { + "type": "text" + }, + "url": { + "type": "text" + }, + "visible_in_picker": { + "type": "boolean" + } + } + }, + "fields": { + "properties": { + "name": { + "type": "text" + }, + "value": { + "type": "text" + }, + "verified_at": { + "type": "date" + } + } + }, + "followers_count": { + "type": "long" + }, + "following_count": { + "type": "long" + }, + "group": { + "type": "boolean" + }, + "header": { + "type": "text" + }, + "header_static": { + "type": "text" + }, + "hide_collections": { + "type": "boolean" + }, + "id": { + "type": "text" + }, + "indexable": { + "type": "boolean" + }, + "last_status_at": { + "type": "date" + }, + "locked": { + "type": "boolean" + }, + "noindex": { + "type": "boolean" + }, + "note": { + "type": "text" + }, + "roles": { + "properties": { + "color": { + "type": "text" + }, + "id": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "statuses_count": { + "type": "long" + }, + "uri": { + "type": "text" + }, + "url": { + "type": "text" + }, + "username": { + "type": "text" + } + } + }, + "noindex": { + "type": "boolean" + }, + "note": { + "type": "text" + }, + "note_text": { + "type": "text" + }, + "other_settings": { + "properties": { + "allow_quote": { + "type": "boolean" + }, + "birth_day": { + "type": "long" + }, + "birth_month": { + "type": "long" + }, + "cat_ears_color": { + "type": "text" + }, + "emoji_reaction_policy": { + "type": "text" + }, + "enable_reaction": { + "type": "boolean" + }, + "hide_followers_count": { + "type": "boolean" + }, + "hide_following_count": { + "type": "boolean" + }, + "hide_network": { + "type": "boolean" + }, + "hide_statuses_count": { + "type": "boolean" + }, + "location": { + "type": "text" + }, + "noai": { + "type": "boolean" + }, + "noindex": { + "type": "boolean" + }, + "subscription_policy": { + "type": "text" + }, + "translatable_private": { + "type": "boolean" + } + } + }, + "payment_options": { + "properties": { + "chain_id": { + "type": "text" + }, + "price": { + "type": "long" + }, + "type": { + "type": "text" + } + } + }, + "pleroma": { + "properties": { + "accepts_chat_messages": { + "type": "boolean" + }, + "also_known_as": { + "type": "text" + }, + "ap_id": { + "type": "text" + }, + "avatar_description": { + "type": "text" + }, + "background_image": { + "type": "text" + }, + "birthday": { + "type": "date" + }, + "favicon": { + "type": "text" + }, + "header_description": { + "type": "text" + }, + "hide_favorites": { + "type": "boolean" + }, + "hide_followers": { + "type": "boolean" + }, + "hide_followers_count": { + "type": "boolean" + }, + "hide_follows": { + "type": "boolean" + }, + "hide_follows_count": { + "type": "boolean" + }, + "is_admin": { + "type": "boolean" + }, + "is_confirmed": { + "type": "boolean" + }, + "is_local": { + "type": "boolean" + }, + "is_moderator": { + "type": "boolean" + }, + "is_suggested": { + "type": "boolean" + }, + "location": { + "type": "text" + }, + "permit_followback": { + "type": "boolean" + }, + "privileges": { + "type": "text" + }, + "relationship": { + "type": "object" + }, + "skip_thread_containment": { + "type": "boolean" + }, + "tags": { + "type": "text" + } + } + }, + "pronouns": { + "type": "text" + }, + "role": { + "properties": { + "name": { + "type": "text" + } + } + }, + "roles": { + "properties": { + "color": { + "type": "text" + }, + "id": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "searchability": { + "type": "text" + }, + "server_features": { + "properties": { + "circle": { + "type": "boolean" + }, + "emoji_reaction": { + "type": "boolean" + }, + "quote": { + "type": "boolean" + }, + "status_reference": { + "type": "boolean" + } + } + }, + "source": { + "properties": { + "fields": { + "properties": { + "name": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "follow_requests_count": { + "type": "long" + }, + "language": { + "type": "text" + }, + "note": { + "type": "text" + }, + "pleroma": { + "properties": { + "actor_type": { + "type": "text" + }, + "discoverable": { + "type": "boolean" + } + } + }, + "privacy": { + "type": "text" + }, + "sensitive": { + "type": "boolean" + } + } + }, + "statuses_count": { + "type": "long" + }, + "subscribable": { + "type": "boolean" + }, + "subscribers_count": { + "type": "long" + }, + "subscribing_count": { + "type": "long" + }, + "suspended": { + "type": "boolean" + }, + "theme": { + "type": "text" + }, + "uri": { + "type": "text" + }, + "url": { + "type": "text" + }, + "username": { + "type": "text" + }, + "website": { + "type": "text" + } + } + }, + "content": { + "type": "text" + }, + "custom_css": { + "type": "text" + }, + "default_approval": { + "type": "boolean" + }, + "description": { + "type": "text" + }, + "description_limit": { + "type": "long" + }, + "description_source": { + "type": "text" + }, + "description_text": { + "type": "text" + }, + "digitalocean-access-key": { + "type": "text" + }, + "discord-client-id": { + "type": "text" + }, + "discord-client-token": { + "type": "text" + }, + "domain": { + "type": "text" + }, + "email": { + "type": "text" + }, + "embargoed": { + "type": "date" + }, + "errors": { + "properties": { + "detail": { + "type": "text" + } + } + }, + "event": { + "properties": { + "code": { + "type": "text" + }, + "name": { + "type": "text" + }, + "rel": { + "type": "text" + }, + "scheme": { + "type": "text" + } + } + }, + "exec": { + "type": "text" + }, + "feature_quote": { + "type": "boolean" + }, + "federated_timeline_restricted": { + "type": "boolean" + }, + "fedibird_capabilities": { + "type": "text" + }, + "finicity-client-key": { + "type": "text" + }, + "finnhub-access-token": { + "type": "text" + }, + "frameworks": { + "properties": { + "additional_0": { + "properties": { + "beamer-api-secret": { + "type": "text" + }, + "compilationOptions": { + "properties": { + "allowUnsafe": { + "type": "boolean" + }, + "flickr-access-token": { + "type": "text" + }, + "github-oauth": { + "type": "text" + }, + "optimize": { + "type": "boolean" + }, + "postman-api-key": { + "type": "text" + }, + "slack-config-access-secret": { + "type": "text" + }, + "warningsAsErrors": { + "type": "boolean" + } + } + }, + "dependencies": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "properties": { + "planetscale-service-secret": { + "type": "text" + }, + "private-key": { + "type": "text" + }, + "sidekiq-sensitive-url": { + "type": "text" + }, + "sumologic-access-key": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "github-oauth": { + "type": "text" + }, + "linkedin-client-key": { + "type": "text" + }, + "shopify-shared-key": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + }, + "zendesk-token-secret": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "alibaba-key-secret": { + "type": "text" + }, + "jfrog-identity-token": { + "type": "text" + }, + "mailgun-pub-token": { + "type": "text" + }, + "new-relic-browser-api-token": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_4": { + "properties": { + "digitalocean-refresh-key": { + "type": "text" + }, + "flickr-access-secret": { + "type": "text" + }, + "gitlab-pat": { + "type": "text" + }, + "telegram-bot-api-secret": { + "type": "text" + } + } + }, + "additional_5": { + "type": "text" + }, + "digitalocean-access-token": { + "type": "text" + }, + "flutterwave-encryption-secret": { + "type": "text" + }, + "kucoin-secret-key": { + "type": "text" + }, + "new-relic-browser-service-key": { + "type": "text" + } + } + }, + "frameworkAssemblies": { + "properties": { + "additional_0": { + "properties": { + "alibaba-access-token-id": { + "type": "text" + }, + "defined-networking-api-secret": { + "type": "text" + }, + "discord-service-token": { + "type": "text" + }, + "sidekiq-sensitive-url": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "flutterwave-public-token": { + "type": "text" + }, + "frameio-service-token": { + "type": "text" + }, + "launchdarkly-access-token": { + "type": "text" + }, + "target": { + "type": "text" + }, + "twitter-access-secret": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "adafruit-service-key": { + "type": "text" + }, + "defined-networking-api-key": { + "type": "text" + }, + "grafana-cloud-service-token": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + }, + "zendesk-token-secret": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "plaid-key-key": { + "type": "text" + }, + "planetscale-oauth-secret": { + "type": "text" + }, + "readme-service-key": { + "type": "text" + }, + "slack-user-secret": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_4": { + "type": "text" + }, + "additional_5": { + "type": "text" + }, + "github-fine-grained-pat": { + "type": "text" + }, + "sendgrid-service-secret": { + "type": "text" + }, + "shopify-private-app-access-key": { + "type": "text" + }, + "stripe-access-token": { + "type": "text" + } + } + }, + "gitlab-pat": { + "type": "text" + }, + "linear-client-token": { + "type": "text" + }, + "planetscale-oauth-token": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "dependencies": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "type": "text" + }, + "additional_3": { + "properties": { + "github-fine-grained-pat": { + "type": "text" + }, + "plaid-api-secret": { + "type": "text" + }, + "plaid-service-token": { + "type": "text" + }, + "sumologic-access-id": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_4": { + "properties": { + "atlassian-service-secret": { + "type": "text" + }, + "bitbucket-client-id": { + "type": "text" + }, + "duffel-service-key": { + "type": "text" + }, + "square-access-secret": { + "type": "text" + } + } + }, + "additional_5": { + "type": "text" + }, + "additional_6": { + "properties": { + "defined-networking-api-secret": { + "type": "text" + }, + "finnhub-access-secret": { + "type": "text" + }, + "grafana-api-account-token": { + "type": "text" + }, + "hubspot-api-secret": { + "type": "text" + }, + "target": { + "type": "text" + } + } + }, + "microsoft-teams-webhook": { + "type": "text" + }, + "plaid-secret-key": { + "type": "text" + }, + "squarespace-access-token": { + "type": "text" + }, + "telegram-bot-api-secret": { + "type": "text" + } + } + }, + "finicity-client-key": { + "type": "text" + }, + "finicity-client-secret": { + "type": "text" + }, + "finnhub-access-token": { + "type": "text" + }, + "rservicedapi-access-token": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "compilationOptions": { + "properties": { + "flickr-access-secret": { + "type": "text" + }, + "github-oauth": { + "type": "text" + }, + "grafana-service-account-key": { + "type": "text" + }, + "private-secret": { + "type": "text" + } + } + }, + "dependencies": { + "properties": { + "additional_0": { + "type": "text" + }, + "asana-client-id": { + "type": "text" + }, + "bittrex-access-token": { + "type": "text" + }, + "grafana-api-account-key": { + "type": "text" + }, + "twitch-api-key": { + "type": "text" + } + } + }, + "frameworkAssemblies": { + "properties": { + "additional_0": { + "properties": { + "github-oauth": { + "type": "text" + }, + "plaid-client-id": { + "type": "text" + }, + "square-access-token": { + "type": "text" + }, + "target": { + "type": "text" + }, + "twilio-service-key": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "contentful-delivery-service-secret": { + "type": "text" + }, + "freshbooks-access-secret": { + "type": "text" + }, + "linear-api-token": { + "type": "text" + }, + "sumologic-access-id": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "authress-api-client-access-token": { + "type": "text" + }, + "gitlab-rrt": { + "type": "text" + }, + "linear-api-secret": { + "type": "text" + }, + "target": { + "type": "text" + }, + "twitter-bearer-token": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "type": "text" + }, + "additional_5": { + "type": "text" + }, + "additional_6": { + "properties": { + "asana-client-id": { + "type": "text" + }, + "dropbox-short-lived-api-token": { + "type": "text" + }, + "sendinblue-api-token": { + "type": "text" + }, + "snyk-service-token": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_7": { + "properties": { + "adobe-client-token": { + "type": "text" + }, + "authress-service-client-access-secret": { + "type": "text" + }, + "finicity-api-token": { + "type": "text" + }, + "openai-service-key": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "doppler-api-key": { + "type": "text" + }, + "easypost-api-token": { + "type": "text" + }, + "mailchimp-service-key": { + "type": "text" + }, + "shopify-custom-access-token": { + "type": "text" + } + } + }, + "gitlab-pat": { + "type": "text" + }, + "sendbird-access-key": { + "type": "text" + }, + "sidekiq-token": { + "type": "text" + }, + "telegram-bot-service-token": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "adafruit-api-secret": { + "type": "text" + }, + "compilationOptions": { + "properties": { + "allowUnsafe": { + "type": "boolean" + }, + "define": { + "type": "text" + }, + "delaySign": { + "type": "boolean" + }, + "discord-client-key": { + "type": "text" + }, + "emitEntryPoint": { + "type": "boolean" + }, + "freshbooks-access-secret": { + "type": "text" + }, + "github-oauth": { + "type": "text" + }, + "keyFile": { + "type": "text" + }, + "languageVersion": { + "type": "text" + }, + "linkedin-client-secret": { + "type": "text" + }, + "optimize": { + "type": "boolean" + }, + "useOssSigning": { + "type": "boolean" + }, + "warningsAsErrors": { + "type": "boolean" + } + } + }, + "dependencies": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "properties": { + "asana-client-token": { + "type": "text" + }, + "jwt": { + "type": "text" + }, + "plaid-api-key": { + "type": "text" + }, + "prefect-api-secret": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "properties": { + "flutterwave-encryption-token": { + "type": "text" + }, + "github-pat": { + "type": "text" + }, + "pulumi-service-secret": { + "type": "text" + }, + "sumologic-access-id": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_5": { + "type": "text" + }, + "additional_6": { + "type": "text" + }, + "additional_7": { + "type": "text" + }, + "defined-networking-service-key": { + "type": "text" + }, + "facebook": { + "type": "text" + }, + "twitter-api-token": { + "type": "text" + }, + "vault-api-token": { + "type": "text" + } + } + }, + "frameworkAssemblies": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "properties": { + "discord-client-secret": { + "type": "text" + }, + "easypost-service-key": { + "type": "text" + }, + "gcp-api-key": { + "type": "text" + }, + "planetscale-oauth-token": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "duffel-service-key": { + "type": "text" + }, + "kucoin-access-key": { + "type": "text" + }, + "rapidapi-access-key": { + "type": "text" + }, + "target": { + "type": "text" + }, + "travisci-access-key": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_4": { + "properties": { + "dynatrace-api-token": { + "type": "text" + }, + "lob-service-token": { + "type": "text" + }, + "shopify-custom-access-key": { + "type": "text" + }, + "target": { + "type": "text" + }, + "twitter-bearer-token": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_5": { + "type": "text" + }, + "facebook": { + "type": "text" + }, + "jfrog-service-secret": { + "type": "text" + }, + "launchdarkly-access-key": { + "type": "text" + }, + "npm-access-key": { + "type": "text" + } + } + }, + "mailchimp-service-secret": { + "type": "text" + }, + "shopify-access-token": { + "type": "text" + }, + "twitter-service-token": { + "type": "text" + } + } + }, + "additional_4": { + "properties": { + "beamer-api-secret": { + "type": "text" + }, + "netlify-access-token": { + "type": "text" + }, + "slack-legacy-bot-secret": { + "type": "text" + }, + "snyk-api-key": { + "type": "text" + } + } + }, + "additional_5": { + "properties": { + "dependencies": { + "properties": { + "additional_0": { + "properties": { + "discord-api-key": { + "type": "text" + }, + "lob-pub-api-token": { + "type": "text" + }, + "messagebird-client-id": { + "type": "text" + }, + "target": { + "type": "text" + }, + "twitter-service-secret": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "alibaba-access-secret-id": { + "type": "text" + }, + "datadog-access-token": { + "type": "text" + }, + "new-relic-user-api-token": { + "type": "text" + }, + "pypi-upload-secret": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_2": { + "type": "text" + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "type": "text" + }, + "age key key": { + "type": "text" + }, + "intercom-service-key": { + "type": "text" + }, + "stripe-access-key": { + "type": "text" + }, + "sumologic-access-id": { + "type": "text" + } + } + }, + "frameworkAssemblies": { + "properties": { + "additional_0": { + "properties": { + "adafruit-api-key": { + "type": "text" + }, + "authress-api-client-access-token": { + "type": "text" + }, + "rubygems-service-token": { + "type": "text" + }, + "shopify-access-secret": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "properties": { + "algolia-api-key": { + "type": "text" + }, + "databricks-api-key": { + "type": "text" + }, + "gitlab-pat": { + "type": "text" + }, + "sumologic-access-id": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "adobe-client-key": { + "type": "text" + }, + "frameio-api-secret": { + "type": "text" + }, + "shopify-custom-access-token": { + "type": "text" + }, + "target": { + "type": "text" + }, + "twitch-api-token": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "codecov-access-token": { + "type": "text" + }, + "droneci-access-secret": { + "type": "text" + }, + "frameio-api-token": { + "type": "text" + }, + "sendgrid-api-key": { + "type": "text" + } + } + }, + "freshbooks-access-key": { + "type": "text" + }, + "linkedin-client-id": { + "type": "text" + }, + "plaid-api-secret": { + "type": "text" + }, + "slack-legacy-workspace-key": { + "type": "text" + } + } + }, + "additional_6": { + "properties": { + "confluent-access-secret": { + "type": "text" + }, + "finnhub-access-secret": { + "type": "text" + }, + "pulumi-api-token": { + "type": "text" + }, + "readme-service-token": { + "type": "text" + } + } + }, + "additional_7": { + "properties": { + "compilationOptions": { + "properties": { + "confluent-access-secret": { + "type": "text" + }, + "dropbox-short-lived-service-secret": { + "type": "text" + }, + "keyFile": { + "type": "text" + }, + "languageVersion": { + "type": "text" + }, + "mailgun-private-api-key": { + "type": "text" + }, + "slack-webhook-url": { + "type": "text" + } + } + }, + "dependencies": { + "properties": { + "additional_0": { + "properties": { + "contentful-delivery-api-token": { + "type": "text" + }, + "jwt": { + "type": "text" + }, + "planetscale-oauth-key": { + "type": "text" + }, + "slack-legacy-bot-secret": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "droneci-access-token": { + "type": "text" + }, + "twilio-api-token": { + "type": "text" + }, + "vault-batch-key": { + "type": "text" + }, + "zendesk-key-key": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "codecov-access-key": { + "type": "text" + }, + "databricks-service-key": { + "type": "text" + }, + "mailgun-pub-secret": { + "type": "text" + }, + "sendbird-access-id": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "type": "text" + }, + "age key key": { + "type": "text" + }, + "droneci-access-token": { + "type": "text" + }, + "slack-user-secret": { + "type": "text" + }, + "travisci-access-secret": { + "type": "text" + } + } + }, + "frameworkAssemblies": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "properties": { + "confluent-access-key": { + "type": "text" + }, + "rservicedapi-access-token": { + "type": "text" + }, + "sendgrid-service-secret": { + "type": "text" + }, + "slack-legacy-bot-secret": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "datadog-access-key": { + "type": "text" + }, + "dropbox-api-token": { + "type": "text" + }, + "easypost-test-api-secret": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "typeform-api-key": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "dropbox-long-lived-service-key": { + "type": "text" + }, + "messagebird-client-id": { + "type": "text" + }, + "new-relic-user-api-secret": { + "type": "text" + }, + "readme-service-secret": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_4": { + "properties": { + "kucoin-token-key": { + "type": "text" + }, + "new-relic-user-service-id": { + "type": "text" + }, + "target": { + "type": "text" + }, + "twilio-service-key": { + "type": "text" + }, + "type": { + "type": "text" + }, + "vault-service-secret": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_5": { + "type": "text" + }, + "additional_6": { + "properties": { + "gitlab-pat": { + "type": "text" + }, + "linkedin-client-id": { + "type": "text" + }, + "pulumi-service-secret": { + "type": "text" + }, + "sidekiq-sensitive-url": { + "type": "text" + }, + "target": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_7": { + "type": "text" + }, + "additional_8": { + "type": "text" + }, + "bitbucket-client-id": { + "type": "text" + }, + "linkedin-client-key": { + "type": "text" + }, + "readme-api-token": { + "type": "text" + }, + "yandex-access-token": { + "type": "text" + } + } + }, + "jfrog-identity-key": { + "type": "text" + }, + "jwt": { + "type": "text" + }, + "plaid-client-id": { + "type": "text" + }, + "twitch-service-token": { + "type": "text" + } + } + }, + "additional_8": { + "properties": { + "beamer-service-secret": { + "type": "text" + }, + "bittrex-secret-token": { + "type": "text" + }, + "kraken-access-secret": { + "type": "text" + }, + "kucoin-key-secret": { + "type": "text" + } + } + }, + "gitlab-pat": { + "type": "text" + }, + "grafana-api-key": { + "type": "text" + }, + "rubygems-api-key": { + "type": "text" + }, + "shopify-custom-access-secret": { + "type": "text" + } + } + }, + "generic-api-token": { + "type": "text" + }, + "generic-service-token": { + "type": "text" + }, + "grafana-api-token": { + "type": "text" + }, + "grafana-cloud-service-secret": { + "type": "text" + }, + "guid": { + "type": "text" + }, + "icon": { + "type": "text" + }, + "imports": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "type": "text" + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "type": "text" + }, + "additional_5": { + "type": "text" + }, + "freshbooks-access-token": { + "type": "text" + }, + "shopify-access-token": { + "type": "text" + }, + "square-access-token": { + "type": "text" + }, + "stripe-access-secret": { + "type": "text" + } + } + }, + "invites_enabled": { + "type": "boolean" + }, + "ipfs_gateway_url": { + "type": "text" + }, + "jfrog-identity-key": { + "type": "text" + }, + "kraken-access-key": { + "type": "text" + }, + "languages": { + "type": "text" + }, + "launchdarkly-access-secret": { + "type": "text" + }, + "linear-service-token": { + "type": "text" + }, + "linkedin-client-key": { + "type": "text" + }, + "logLevel": { + "type": "long" + }, + "login_message": { + "type": "text" + }, + "mailgun-signing-key": { + "type": "text" + }, + "max_media_attachments": { + "type": "long" + }, + "max_post_chars": { + "type": "long" + }, + "max_toot_chars": { + "type": "long" + }, + "name": { + "type": "text" + }, + "new_accounts_read_only": { + "type": "boolean" + }, + "nostr": { + "properties": { + "pow": { + "properties": { + "registrations": { + "type": "long" + } + } + }, + "pubkey": { + "type": "text" + }, + "relay": { + "type": "text" + } + } + }, + "npm-access-secret": { + "type": "text" + }, + "object": { + "properties": { + "code": { + "type": "text" + }, + "name": { + "type": "text" + }, + "rel": { + "type": "text" + }, + "scheme": { + "type": "text" + } + } + }, + "packInclude": { + "properties": { + "easypost-api-key": { + "type": "text" + }, + "plaid-api-key": { + "type": "text" + }, + "planetscale-oauth-key": { + "type": "text" + }, + "vault-service-key": { + "type": "text" + } + } + }, + "pleroma": { + "properties": { + "favicon": { + "type": "text" + }, + "metadata": { + "properties": { + "account_activation_required": { + "type": "boolean" + }, + "birthday_min_age": { + "type": "long" + }, + "birthday_required": { + "type": "boolean" + }, + "features": { + "type": "text" + }, + "federation": { + "properties": { + "enabled": { + "type": "boolean" + }, + "exclusions": { + "type": "boolean" + }, + "mrf_bash_bot": { + "properties": { + "bot_account_nickname": { + "type": "text" + }, + "commands": { + "type": "text" + } + } + }, + "mrf_block_notification_policy": { + "type": "text" + }, + "mrf_emoji": { + "properties": { + "remove_shortcode": { + "type": "text" + } + } + }, + "mrf_hashtag": { + "properties": { + "federated_timeline_removal": { + "type": "text" + }, + "reject": { + "type": "text" + }, + "sensitive": { + "type": "text" + } + } + }, + "mrf_hellthread": { + "properties": { + "delist_threshold": { + "type": "long" + }, + "reject_threshold": { + "type": "long" + } + } + }, + "mrf_keyword": { + "properties": { + "federated_timeline_removal": { + "type": "text" + }, + "reject": { + "type": "text" + }, + "replace": { + "properties": { + "pattern": { + "type": "text" + }, + "replacement": { + "type": "text" + } + } + } + } + }, + "mrf_nsfw_api": { + "properties": { + "mark_sensitive": { + "type": "boolean" + }, + "reject": { + "type": "boolean" + }, + "threshold": { + "type": "float" + }, + "unlist": { + "type": "boolean" + } + } + }, + "mrf_object_age": { + "properties": { + "actions": { + "type": "text" + }, + "threshold": { + "type": "long" + } + } + }, + "mrf_policies": { + "type": "text" + }, + "mrf_rejectnonpublic": { + "properties": { + "allow_direct": { + "type": "boolean" + }, + "allow_followersonly": { + "type": "boolean" + } + } + }, + "mrf_remote_report": { + "properties": { + "reject_all": { + "type": "boolean" + }, + "reject_anonymous": { + "type": "boolean" + }, + "reject_empty_message": { + "type": "boolean" + } + } + }, + "mrf_sample": { + "properties": { + "content": { + "type": "text" + } + } + }, + "mrf_simple": { + "properties": { + "accept": { + "type": "text" + }, + "avatar_removal": { + "type": "text" + }, + "banner_removal": { + "type": "text" + }, + "federated_timeline_removal": { + "type": "text" + }, + "followers_only": { + "type": "text" + }, + "media_nsfw": { + "type": "text" + }, + "media_removal": { + "type": "text" + }, + "reject": { + "type": "text" + }, + "reject_deletes": { + "type": "text" + }, + "report_removal": { + "type": "text" + } + } + }, + "mrf_simple_info": { + "properties": { + "accept": { + "type": "object", + "enabled": false + }, + "avatar_removal": { + "type": "object", + "enabled": false + }, + "banner_removal": { + "type": "object", + "enabled": false + }, + "federated_timeline_removal": { + "type": "object", + "enabled": false + }, + "followers_only": { + "type": "object", + "enabled": false + }, + "media_nsfw": { + "type": "object", + "enabled": false + }, + "media_removal": { + "type": "object", + "enabled": false + }, + "reject": { + "type": "object", + "enabled": false + }, + "reject_deletes": { + "type": "object", + "enabled": false + }, + "report_removal": { + "type": "object", + "enabled": false + } + } + }, + "mrf_user_allowlist": { + "type": "object" + }, + "mrf_vocabulary": { + "type": "object" + }, + "quarantined_instances": { + "type": "object", + "enabled": false + }, + "quarantined_instances_info": { + "properties": { + "quarantined_instances": { + "type": "object", + "enabled": false + } + } + }, + "rejected_instances": { + "type": "object", + "enabled": false + } + } + }, + "fields_limits": { + "properties": { + "max_fields": { + "type": "long" + }, + "max_remote_fields": { + "type": "long" + }, + "name_length": { + "type": "long" + }, + "value_length": { + "type": "long" + } + } + }, + "markup": { + "properties": { + "allow_headings": { + "type": "boolean" + }, + "allow_inline_images": { + "type": "boolean" + }, + "allow_tables": { + "type": "boolean" + } + } + }, + "migration_cooldown_period": { + "type": "long" + }, + "post_formats": { + "type": "text" + }, + "privileged_staff": { + "type": "boolean" + }, + "restrict_unauthenticated": { + "properties": { + "activities": { + "properties": { + "local": { + "type": "boolean" + }, + "remote": { + "type": "boolean" + } + } + }, + "profiles": { + "properties": { + "local": { + "type": "boolean" + }, + "remote": { + "type": "boolean" + } + } + }, + "timelines": { + "properties": { + "federated": { + "type": "boolean" + }, + "local": { + "type": "boolean" + } + } + } + } + }, + "translation": { + "properties": { + "allow_remote": { + "type": "boolean" + }, + "allow_unauthenticated": { + "type": "boolean" + }, + "source_languages": { + "type": "text" + }, + "target_languages": { + "type": "text" + } + } + } + } + }, + "oauth_consumer_strategies": { + "type": "text" + }, + "stats": { + "properties": { + "mau": { + "type": "long" + } + } + }, + "vapid_public_key": { + "type": "text" + } + } + }, + "poll_limits": { + "properties": { + "max_expiration": { + "type": "double" + }, + "max_option_chars": { + "type": "long" + }, + "max_options": { + "type": "long" + }, + "min_expiration": { + "type": "double" + }, + "min_options": { + "type": "long" + } + } + }, + "postman-service-token": { + "type": "text" + }, + "profile": { + "type": "text" + }, + "profiles": { + "properties": { + "freshbooks-access-secret": { + "type": "text" + }, + "linkedin-client-secret": { + "type": "text" + }, + "mailchimp-api-secret": { + "type": "text" + }, + "twitter-service-token": { + "type": "text" + } + } + }, + "pulumi-service-secret": { + "type": "text" + }, + "readme-api-token": { + "type": "text" + }, + "registrations": { + "type": "boolean" + }, + "repository": { + "properties": { + "fastly-service-token": { + "type": "text" + }, + "finicity-client-token": { + "type": "text" + }, + "gitlab-ptt": { + "type": "text" + }, + "huggingface-organization-api-key": { + "type": "text" + } + } + }, + "requireLicenseAcceptance": { + "type": "boolean" + }, + "resource": { + "type": "text" + }, + "resourceFiles": { + "type": "text" + }, + "rubygems-service-token": { + "type": "text" + }, + "rules": { + "properties": { + "hint": { + "type": "text" + }, + "id": { + "type": "text" + }, + "text": { + "type": "text" + } + } + }, + "scenarios": { + "properties": { + "flow": { + "properties": { + "delete": { + "properties": { + "expect": { + "properties": { + "statusCode": { + "type": "long" + } + } + }, + "headers": { + "properties": { + "accept": { + "type": "text" + }, + "api_key": { + "type": "text" + }, + "contentType": { + "type": "text" + } + } + }, + "name": { + "type": "text" + }, + "url": { + "type": "text" + } + } + }, + "function": { + "type": "text" + }, + "get": { + "properties": { + "expect": { + "properties": { + "statusCode": { + "type": "long" + } + } + }, + "headers": { + "properties": { + "accept": { + "type": "text" + }, + "contentType": { + "type": "text" + } + } + }, + "name": { + "type": "text" + }, + "qs": { + "properties": { + "password": { + "type": "text" + }, + "username": { + "type": "text" + } + } + }, + "url": { + "type": "text" + } + } + }, + "post": { + "properties": { + "capture": { + "properties": { + "as": { + "type": "text" + }, + "json": { + "type": "text" + } + } + }, + "expect": { + "properties": { + "statusCode": { + "type": "long" + } + } + }, + "headers": { + "properties": { + "Content-Type": { + "type": "text" + }, + "accept": { + "type": "text" + } + } + }, + "json": { + "properties": { + "category": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "type": "text" + } + } + }, + "email": { + "type": "text" + }, + "firstName": { + "type": "text" + }, + "id": { + "type": "long" + }, + "lastName": { + "type": "text" + }, + "name": { + "type": "text" + }, + "password": { + "type": "text" + }, + "phone": { + "type": "text" + }, + "photoUrls": { + "type": "text" + }, + "status": { + "type": "text" + }, + "tags": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "type": "text" + } + } + }, + "userStatus": { + "type": "long" + }, + "username": { + "type": "text" + } + } + }, + "name": { + "type": "text" + }, + "url": { + "type": "text" + } + } + } + } + } + } + }, + "scopes": { + "properties": { + "additional_0": { + "properties": { + "beamer-api-secret": { + "type": "text" + }, + "jfrog-service-key": { + "type": "text" + }, + "private-secret": { + "type": "text" + }, + "twitter-service-secret": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "type": "text" + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "type": "text" + }, + "additional_5": { + "type": "text" + }, + "additional_6": { + "type": "text" + }, + "additional_7": { + "type": "text" + }, + "additional_8": { + "type": "text" + }, + "easypost-api-secret": { + "type": "text" + }, + "flutterwave-public-token": { + "type": "text" + }, + "huggingface-access-key": { + "type": "text" + }, + "new-relic-user-service-key": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "type": "text" + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "type": "text" + }, + "additional_5": { + "type": "text" + }, + "finnhub-access-secret": { + "type": "text" + }, + "github-oauth": { + "type": "text" + }, + "grafana-service-account-secret": { + "type": "text" + }, + "shopify-private-app-access-secret": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "type": "text" + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "type": "text" + }, + "additional_5": { + "type": "text" + }, + "additional_6": { + "type": "text" + }, + "additional_7": { + "type": "text" + }, + "mailgun-pub-token": { + "type": "text" + }, + "square-access-secret": { + "type": "text" + }, + "twitter-api-secret": { + "type": "text" + }, + "yandex-aws-access-secret": { + "type": "text" + } + } + }, + "easypost-test-api-key": { + "type": "text" + }, + "github-oauth": { + "type": "text" + }, + "linkedin-client-id": { + "type": "text" + }, + "mailgun-private-service-secret": { + "type": "text" + } + } + }, + "sendbird-access-token": { + "type": "text" + }, + "sentry-access-secret": { + "type": "text" + }, + "shared": { + "type": "text" + }, + "shopify-shared-secret": { + "type": "text" + }, + "short_description": { + "type": "text" + }, + "short_description_text": { + "type": "text" + }, + "shout_limit": { + "type": "long" + }, + "sidekiq-sensitive-url": { + "type": "text" + }, + "slack-config-access-secret": { + "type": "text" + }, + "slack-config-refresh-key": { + "type": "text" + }, + "slack-legacy-workspace-token": { + "type": "text" + }, + "snyk-service-key": { + "type": "text" + }, + "snyk-service-secret": { + "type": "text" + }, + "soapbox": { + "properties": { + "version": { + "type": "text" + } + } + }, + "software_name": { + "type": "text" + }, + "software_version": { + "type": "text" + }, + "source_url": { + "type": "text" + }, + "stats": { + "properties": { + "domain_count": { + "type": "integer" + }, + "remote_user_count": { + "type": "integer" + }, + "status_count": { + "type": "integer" + }, + "user_count": { + "type": "integer" + } + } + }, + "strictSSL": { + "type": "boolean" + }, + "subject": { + "properties": { + "code": { + "type": "text" + }, + "name": { + "type": "text" + }, + "rel": { + "type": "text" + }, + "scheme": { + "type": "text" + } + } + }, + "sumologic-access-key": { + "type": "text" + }, + "tags": { + "type": "text" + }, + "terms": { + "type": "text" + }, + "terms_text": { + "type": "text" + }, + "thumbnail": { + "type": "text" + }, + "thumbnail_description": { + "type": "text" + }, + "thumbnail_static": { + "type": "text" + }, + "thumbnail_static_type": { + "type": "text" + }, + "thumbnail_type": { + "type": "text" + }, + "title": { + "type": "text" + }, + "token": { + "type": "text" + }, + "trustindicator": { + "properties": { + "code": { + "type": "text" + } + } + }, + "twitter-service-secret": { + "type": "text" + }, + "upload_limit": { + "type": "long" + }, + "uri": { + "type": "text" + }, + "urls": { + "properties": { + "streaming_api": { + "type": "text" + } + } + }, + "version": { + "type": "text" + }, + "wordmark": { + "type": "text" + }, + "wordmark_dark": { + "type": "text" + }, + "yandex-service-token": { + "type": "text" + } + } + }, + "blocks": { + "type": "nested", + "properties": { + "accountId": { + "type": "keyword" + }, + "accountUid": { + "type": "keyword" + }, + "additional_public_assets": { + "type": "text" + }, + "adobe-client-secret": { + "type": "text" + }, + "algolia-service-token": { + "type": "text" + }, + "aliases": { + "type": "text" + }, + "asset_name": { + "type": "text" + }, + "builders": { + "properties": { + "additional_0": { + "properties": { + "adafruit-service-secret": { + "type": "text" + }, + "builder_factories": { + "type": "text" + }, + "defaults": { + "properties": { + "defined-networking-api-token": { + "type": "text" + }, + "dev_options": { + "properties": { + "grafana-api-account-secret": { + "type": "text" + }, + "hubspot-api-token": { + "type": "text" + }, + "mapbox-api-key": { + "type": "text" + }, + "plaid-client-id": { + "type": "text" + } + } + }, + "digitalocean-refresh-secret": { + "type": "text" + }, + "finicity-client-secret": { + "type": "text" + }, + "openai-api-key": { + "type": "text" + }, + "options": { + "properties": { + "easypost-test-service-secret": { + "type": "text" + }, + "gitter-access-secret": { + "type": "text" + }, + "huggingface-organization-service-token": { + "type": "text" + }, + "private-key": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "dynatrace-api-token": { + "type": "text" + }, + "etsy-access-secret": { + "type": "text" + }, + "finicity-service-token": { + "type": "text" + }, + "freshbooks-access-key": { + "type": "text" + } + } + } + } + }, + "grafana-api-secret": { + "type": "text" + }, + "hubspot-service-secret": { + "type": "text" + }, + "twitch-api-key": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "builder_factories": { + "type": "text" + }, + "finnhub-access-secret": { + "type": "text" + }, + "is_optional": { + "type": "boolean" + }, + "mapbox-api-secret": { + "type": "text" + }, + "runs_before": { + "type": "text" + }, + "sumologic-access-key": { + "type": "text" + }, + "target": { + "type": "text" + }, + "twitter-service-key": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "build_to": { + "type": "text" + }, + "builder_factories": { + "type": "text" + }, + "okta-access-secret": { + "type": "text" + }, + "readme-api-token": { + "type": "text" + }, + "shopify-private-app-access-key": { + "type": "text" + }, + "twitter-bearer-secret": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "applies_builders": { + "type": "text" + }, + "auto_apply": { + "type": "text" + }, + "build_extensions": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "discord-api-token": { + "type": "text" + }, + "mapbox-service-token": { + "type": "text" + }, + "openai-api-secret": { + "type": "text" + }, + "slack-user-secret": { + "type": "text" + } + } + }, + "doppler-api-key": { + "type": "text" + }, + "freshbooks-access-token": { + "type": "text" + }, + "planetscale-password": { + "type": "text" + }, + "rubygems-api-token": { + "type": "text" + }, + "runs_before": { + "type": "text" + } + } + }, + "additional_4": { + "properties": { + "mailchimp-api-key": { + "type": "text" + }, + "netlify-access-token": { + "type": "text" + }, + "pulumi-service-key": { + "type": "text" + }, + "slack-webhook-url": { + "type": "text" + }, + "target": { + "type": "text" + } + } + }, + "additional_5": { + "properties": { + "auto_apply": { + "type": "text" + }, + "build_extensions": { + "properties": { + "clojars-service-key": { + "type": "text" + }, + "facebook": { + "type": "text" + }, + "gcp-api-token": { + "type": "text" + }, + "linear-api-secret": { + "type": "text" + } + } + }, + "builder_factories": { + "type": "text" + }, + "finicity-client-secret": { + "type": "text" + }, + "flutterwave-public-token": { + "type": "text" + }, + "is_optional": { + "type": "boolean" + }, + "shopify-custom-access-key": { + "type": "text" + }, + "twitter-api-secret": { + "type": "text" + } + } + }, + "mailchimp-api-secret": { + "type": "text" + }, + "shopify-shared-token": { + "type": "text" + }, + "sidekiq-sensitive-url": { + "type": "text" + } + } + }, + "channelNo": { + "type": "long" + }, + "channelType": { + "type": "keyword" + }, + "coinbase-access-token": { + "type": "text" + }, + "comment": { + "type": "keyword" + }, + "community_bridge": { + "type": "text" + }, + "contentful-delivery-api-secret": { + "type": "text" + }, + "crates": { + "properties": { + "deps": { + "properties": { + "crate": { + "type": "long" + }, + "name": { + "type": "text" + } + } + }, + "display_name": { + "type": "text" + }, + "edition": { + "type": "text" + }, + "is_proc_macro": { + "type": "boolean" + }, + "is_workspace_member": { + "type": "boolean" + }, + "repository": { + "type": "text" + }, + "root_module": { + "type": "text" + }, + "target": { + "type": "text" + } + } + }, + "created_at": { + "type": "date" + }, + "dependencies": { + "properties": { + "additional_0": { + "properties": { + "droneci-access-key": { + "type": "text" + }, + "lob-service-key": { + "type": "text" + }, + "plaid-key-token": { + "type": "text" + }, + "twilio-api-key": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "doppler-service-secret": { + "type": "text" + }, + "github-fine-grained-pat": { + "type": "text" + }, + "grafana-service-account-secret": { + "type": "text" + }, + "planetscale-password": { + "type": "text" + }, + "type": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "clojars-api-key": { + "type": "text" + }, + "easypost-api-secret": { + "type": "text" + }, + "slack-user-key": { + "type": "text" + }, + "yandex-service-secret": { + "type": "text" + } + } + }, + "description": { + "properties": { + "color": { + "type": "text" + }, + "confluent-access-secret": { + "type": "text" + }, + "github-pat": { + "type": "text" + }, + "linkedin-client-token": { + "type": "text" + }, + "sumologic-access-id": { + "type": "text" + }, + "translate": { + "type": "text" + } + } + }, + "digest": { + "type": "keyword" + }, + "digitalocean-pat": { + "type": "text" + }, + "digitalocean-refresh-token": { + "type": "text" + }, + "domain": { + "type": "keyword" + }, + "droneci-access-secret": { + "type": "text" + }, + "dropbox-short-lived-service-key": { + "type": "text" + }, + "dynatrace-service-token": { + "type": "text" + }, + "exceptionLogging": { + "type": "text" + }, + "expires": { + "type": "date" + }, + "fields": { + "type": "text" + }, + "finicity-client-token": { + "type": "text" + }, + "finnhub-access-secret": { + "type": "text" + }, + "flutterwave-token-key": { + "type": "text" + }, + "generic-service-token": { + "type": "text" + }, + "github-fine-grained-pat": { + "type": "text" + }, + "github-oauth": { + "type": "text" + }, + "global_options": { + "properties": { + "additional_0": { + "properties": { + "dev_options": { + "properties": { + "atlassian-api-key": { + "type": "text" + }, + "authress-service-client-access-secret": { + "type": "text" + }, + "sendgrid-service-secret": { + "type": "text" + }, + "twilio-service-token": { + "type": "text" + } + } + }, + "finicity-client-key": { + "type": "text" + }, + "gocardless-api-key": { + "type": "text" + }, + "kucoin-access-secret": { + "type": "text" + }, + "options": { + "properties": { + "bitbucket-client-id": { + "type": "text" + }, + "discord-client-id": { + "type": "text" + }, + "freshbooks-access-token": { + "type": "text" + }, + "twitter-service-key": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "alibaba-access-secret-id": { + "type": "text" + }, + "linkedin-client-id": { + "type": "text" + }, + "linkedin-client-token": { + "type": "text" + }, + "mailgun-private-api-key": { + "type": "text" + } + } + }, + "telegram-bot-api-key": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "dev_options": { + "properties": { + "github-app-key": { + "type": "text" + }, + "hashicorp-tf-service-token": { + "type": "text" + }, + "huggingface-organization-service-key": { + "type": "text" + }, + "yandex-service-key": { + "type": "text" + } + } + }, + "gcp-api-secret": { + "type": "text" + }, + "grafana-service-account-token": { + "type": "text" + }, + "linkedin-client-key": { + "type": "text" + }, + "options": { + "properties": { + "alibaba-token-token": { + "type": "text" + }, + "finicity-api-token": { + "type": "text" + }, + "sendbird-access-id": { + "type": "text" + }, + "sendgrid-api-token": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "bittrex-key-secret": { + "type": "text" + }, + "fastly-api-key": { + "type": "text" + }, + "gcp-service-token": { + "type": "text" + }, + "rubygems-api-key": { + "type": "text" + } + } + }, + "runs_before": { + "type": "text" + }, + "yandex-access-token": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "dev_options": { + "properties": { + "github-refresh-key": { + "type": "text" + }, + "jfrog-api-secret": { + "type": "text" + }, + "linear-service-token": { + "type": "text" + }, + "linkedin-client-secret": { + "type": "text" + } + } + }, + "huggingface-access-key": { + "type": "text" + }, + "options": { + "properties": { + "databricks-api-key": { + "type": "text" + }, + "defined-networking-service-token": { + "type": "text" + }, + "digitalocean-refresh-key": { + "type": "text" + }, + "pulumi-api-secret": { + "type": "text" + } + } + }, + "plaid-token-key": { + "type": "text" + }, + "release_options": { + "properties": { + "flickr-access-token": { + "type": "text" + }, + "grafana-cloud-api-key": { + "type": "text" + }, + "mapbox-service-token": { + "type": "text" + }, + "readme-service-key": { + "type": "text" + } + } + }, + "scalingo-api-secret": { + "type": "text" + }, + "square-access-token": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "beamer-api-key": { + "type": "text" + }, + "github-oauth": { + "type": "text" + }, + "mailgun-pub-key": { + "type": "text" + }, + "options": { + "properties": { + "adobe-client-id": { + "type": "text" + }, + "beamer-api-token": { + "type": "text" + }, + "gitlab-ptt": { + "type": "text" + }, + "scalingo-api-token": { + "type": "text" + } + } + }, + "runs_before": { + "type": "text" + }, + "twitter-bearer-secret": { + "type": "text" + } + } + }, + "additional_4": { + "properties": { + "dev_options": { + "properties": { + "bittrex-access-key": { + "type": "text" + }, + "doppler-service-token": { + "type": "text" + }, + "kucoin-access-key": { + "type": "text" + }, + "mailgun-private-api-secret": { + "type": "text" + } + } + }, + "fastly-api-secret": { + "type": "text" + }, + "hashicorp-tf-service-key": { + "type": "text" + }, + "huggingface-access-key": { + "type": "text" + }, + "lob-pub-api-token": { + "type": "text" + }, + "options": { + "properties": { + "contentful-delivery-service-token": { + "type": "text" + }, + "intercom-api-token": { + "type": "text" + }, + "launchdarkly-access-secret": { + "type": "text" + }, + "linear-client-secret": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "clojars-api-token": { + "type": "text" + }, + "linkedin-client-token": { + "type": "text" + }, + "rubygems-api-key": { + "type": "text" + }, + "yandex-api-key": { + "type": "text" + } + } + } + } + }, + "additional_5": { + "properties": { + "alibaba-secret-key": { + "type": "text" + }, + "dev_options": { + "properties": { + "github-refresh-secret": { + "type": "text" + }, + "private-secret": { + "type": "text" + }, + "rservicedapi-access-secret": { + "type": "text" + }, + "slack-app-secret": { + "type": "text" + } + } + }, + "lob-pub-api-token": { + "type": "text" + }, + "npm-access-secret": { + "type": "text" + }, + "options": { + "properties": { + "discord-client-key": { + "type": "text" + }, + "nytimes-access-token": { + "type": "text" + }, + "scalingo-api-token": { + "type": "text" + }, + "slack-legacy-workspace-secret": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "bittrex-token-key": { + "type": "text" + }, + "easypost-test-api-secret": { + "type": "text" + }, + "hashicorp-tf-service-key": { + "type": "text" + }, + "zendesk-key-secret": { + "type": "text" + } + } + }, + "runs_before": { + "type": "text" + }, + "slack-legacy-workspace-key": { + "type": "text" + } + } + }, + "additional_6": { + "properties": { + "airtable-api-key": { + "type": "text" + }, + "shopify-custom-access-key": { + "type": "text" + }, + "sidekiq-sensitive-url": { + "type": "text" + }, + "vault-api-key": { + "type": "text" + } + } + }, + "algolia-service-token": { + "type": "text" + }, + "bittrex-key-key": { + "type": "text" + }, + "digitalocean-refresh-token": { + "type": "text" + }, + "flickr-access-secret": { + "type": "text" + }, + "gcp-service-key": { + "type": "text" + }, + "slack-user-secret": { + "type": "text" + }, + "sumologic-access-secret": { + "type": "text" + }, + "yandex-access-key": { + "type": "text" + } + } + }, + "grade": { + "type": "keyword" + }, + "grafana-service-account-token": { + "type": "text" + }, + "heroku-api-key": { + "type": "text" + }, + "heroku-service-secret": { + "type": "text" + }, + "hubspot-api-secret": { + "type": "text" + }, + "id": { + "type": "integer" + }, + "ingredient": { + "type": "text" + }, + "item_model_index": { + "type": "float" + }, + "jfrog-api-secret": { + "type": "text" + }, + "kucoin-access-key": { + "type": "text" + }, + "launchdarkly-access-key": { + "type": "text" + }, + "linear-client-token": { + "type": "text" + }, + "linkedin-client-id": { + "type": "text" + }, + "linkedin-client-token": { + "type": "text" + }, + "links": { + "properties": { + "href": { + "type": "text" + }, + "properties": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "type": "text" + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "type": "text" + }, + "additional_5": { + "type": "text" + }, + "additional_6": { + "type": "text" + }, + "additional_8": { + "type": "text" + } + } + }, + "rel": { + "type": "text" + }, + "template": { + "type": "text" + }, + "titles": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "type": "text" + }, + "additional_3": { + "type": "text" + }, + "additional_4": { + "type": "text" + }, + "additional_5": { + "type": "text" + }, + "additional_6": { + "type": "text" + }, + "additional_7": { + "type": "text" + }, + "default": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "name": { + "type": "keyword" + }, + "new-relic-user-api-id": { + "type": "text" + }, + "new-relic-user-api-key": { + "type": "text" + }, + "nytimes-access-key": { + "type": "text" + }, + "openai-service-key": { + "type": "text" + }, + "owners": { + "properties": { + "email": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "patreon": { + "type": "text" + }, + "plaid-api-token": { + "type": "text" + }, + "planetscale-api-key": { + "type": "text" + }, + "planetscale-api-secret": { + "type": "text" + }, + "planetscale-oauth-token": { + "type": "text" + }, + "project": { + "properties": { + "gitlab-pat": { + "type": "text" + }, + "jwt": { + "type": "text" + }, + "name": { + "type": "text" + }, + "settings": { + "type": "text" + }, + "twitter-access-secret": { + "type": "text" + }, + "yandex-access-token": { + "type": "text" + } + } + }, + "properties": { + "properties": { + "additional_0": { + "type": "text" + }, + "additional_1": { + "type": "text" + }, + "additional_2": { + "type": "text" + }, + "bittrex-access-token": { + "type": "text" + }, + "flutterwave-secret-token": { + "type": "text" + }, + "new-relic-browser-service-key": { + "type": "text" + }, + "shopify-access-token": { + "type": "text" + } + } + }, + "repositoryID": { + "type": "text" + }, + "representativeImageUrl": { + "type": "keyword" + }, + "rubygems-service-key": { + "type": "text" + }, + "rules": { + "properties": { + "allowData": { + "type": "boolean" + }, + "path": { + "type": "text" + }, + "regex": { + "type": "text" + }, + "types": { + "type": "text" + } + } + }, + "sendbird-access-id": { + "type": "text" + }, + "sentry-access-key": { + "type": "text" + }, + "severity": { + "type": "keyword" + }, + "severity_ex": { + "type": "keyword" + }, + "shippo-api-secret": { + "type": "text" + }, + "shopify-custom-access-key": { + "type": "text" + }, + "shopify-custom-access-secret": { + "type": "text" + }, + "sidekiq-secret": { + "type": "text" + }, + "slack-bot-token": { + "type": "text" + }, + "slack-config-access-token": { + "type": "text" + }, + "slack-config-refresh-secret": { + "type": "text" + }, + "slack-legacy-workspace-key": { + "type": "text" + }, + "solution": { + "properties": { + "linear-api-key": { + "type": "text" + }, + "path": { + "type": "text" + }, + "postman-api-token": { + "type": "text" + }, + "projects": { + "type": "text" + }, + "pulumi-api-secret": { + "type": "text" + }, + "sentry-access-secret": { + "type": "text" + } + } + }, + "stages": { + "properties": { + "5wo19kq0 7BnVPMLpu kBu 7D6LpVh3dK AkqIgQo Kd hG6K a2Nz": { + "properties": { + "age key token": { + "type": "text" + }, + "grafana-api-account-token": { + "type": "text" + }, + "slack-config-refresh-secret": { + "type": "text" + }, + "stripe-access-key": { + "type": "text" + }, + "when": { + "properties": { + "branch": { + "type": "text" + }, + "gitlab-rrt": { + "type": "text" + }, + "mailgun-pub-token": { + "type": "text" + }, + "slack-app-token": { + "type": "text" + }, + "twitter-access-key": { + "type": "text" + } + } + } + } + }, + "PaD9J xHqx7 jSYSTOUA sL6 vncImsPf bgx": { + "properties": { + "flutterwave-token-token": { + "type": "text" + }, + "huggingface-organization-service-secret": { + "type": "text" + }, + "huggingface-organization-service-token": { + "type": "text" + }, + "sendbird-access-id": { + "type": "text" + } + } + }, + "duffel-api-token": { + "type": "text" + }, + "sentry-access-key": { + "type": "text" + }, + "travisci-access-key": { + "type": "text" + }, + "yandex-aws-access-token": { + "type": "text" + } + } + }, + "stock_auto": { + "type": "boolean" + }, + "stripe-access-secret": { + "type": "text" + }, + "subject": { + "type": "text" + }, + "sumologic-access-token": { + "type": "text" + }, + "targets": { + "properties": { + "additional_0": { + "properties": { + "atlassian-api-key": { + "type": "text" + }, + "auto_apply_builders": { + "type": "boolean" + }, + "bitbucket-client-key": { + "type": "text" + }, + "builders": { + "properties": { + "additional_0": { + "properties": { + "confluent-key-secret": { + "type": "text" + }, + "dev_options": { + "properties": { + "flutterwave-key-token": { + "type": "text" + }, + "scalingo-service-token": { + "type": "text" + }, + "slack-legacy-workspace-secret": { + "type": "text" + }, + "square-access-secret": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "jwt-base64": { + "type": "text" + }, + "options": { + "properties": { + "airtable-service-key": { + "type": "text" + }, + "datadog-access-secret": { + "type": "text" + }, + "gocardless-service-secret": { + "type": "text" + }, + "private-secret": { + "type": "text" + } + } + }, + "plaid-service-token": { + "type": "text" + }, + "release_options": { + "properties": { + "clojars-service-secret": { + "type": "text" + }, + "github-pat": { + "type": "text" + }, + "launchdarkly-access-key": { + "type": "text" + }, + "nytimes-access-secret": { + "type": "text" + } + } + }, + "sendgrid-service-secret": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "asana-client-id": { + "type": "text" + }, + "dev_options": { + "properties": { + "atlassian-api-secret": { + "type": "text" + }, + "contentful-delivery-service-key": { + "type": "text" + }, + "sendbird-access-id": { + "type": "text" + }, + "yandex-service-key": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "npm-access-token": { + "type": "text" + }, + "options": { + "properties": { + "github-refresh-secret": { + "type": "text" + }, + "rapidapi-access-token": { + "type": "text" + }, + "slack-legacy-secret": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "dropbox-long-lived-service-token": { + "type": "text" + }, + "flickr-access-token": { + "type": "text" + }, + "rapidservice-access-key": { + "type": "text" + }, + "typeform-api-key": { + "type": "text" + } + } + }, + "shopify-access-token": { + "type": "text" + }, + "telegram-bot-service-key": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "defined-networking-service-secret": { + "type": "text" + }, + "sentry-access-key": { + "type": "text" + }, + "slack-legacy-bot-token": { + "type": "text" + }, + "twitch-api-token": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "authress-api-client-access-secret": { + "type": "text" + }, + "codecov-access-key": { + "type": "text" + }, + "dev_options": { + "properties": { + "digitalocean-refresh-key": { + "type": "text" + }, + "jwt": { + "type": "text" + }, + "mapbox-api-token": { + "type": "text" + }, + "shopify-shared-secret": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "generate_for": { + "properties": { + "alibaba-key-token": { + "type": "text" + }, + "digitalocean-pat": { + "type": "text" + }, + "dynatrace-service-token": { + "type": "text" + }, + "squarespace-access-token": { + "type": "text" + } + } + }, + "microsoft-teams-webhook": { + "type": "text" + }, + "options": { + "properties": { + "algolia-api-token": { + "type": "text" + }, + "asana-client-id": { + "type": "text" + }, + "messagebird-service-token": { + "type": "text" + }, + "shopify-access-token": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "digitalocean-refresh-secret": { + "type": "text" + }, + "gcp-service-secret": { + "type": "text" + }, + "plaid-api-key": { + "type": "text" + }, + "planetscale-service-secret": { + "type": "text" + } + } + }, + "slack-legacy-bot-token": { + "type": "text" + } + } + }, + "clojars-service-key": { + "type": "text" + }, + "finicity-client-token": { + "type": "text" + }, + "linear-api-token": { + "type": "text" + }, + "slack-webhook-url": { + "type": "text" + } + } + }, + "dependencies": { + "type": "text" + }, + "linear-service-key": { + "type": "text" + }, + "new-relic-browser-api-secret": { + "type": "text" + }, + "plaid-client-id": { + "type": "text" + }, + "readme-service-key": { + "type": "text" + }, + "sources": { + "properties": { + "droneci-access-key": { + "type": "text" + }, + "flickr-access-secret": { + "type": "text" + }, + "hubspot-service-key": { + "type": "text" + }, + "launchdarkly-access-token": { + "type": "text" + }, + "mailchimp-service-token": { + "type": "text" + }, + "mapbox-service-secret": { + "type": "text" + }, + "sidekiq-sensitive-url": { + "type": "text" + }, + "typeform-service-secret": { + "type": "text" + } + } + }, + "twitter-service-secret": { + "type": "text" + }, + "yandex-api-key": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "beamer-api-secret": { + "type": "text" + }, + "dependencies": { + "type": "text" + }, + "discord-client-key": { + "type": "text" + }, + "easypost-test-api-token": { + "type": "text" + }, + "gitlab-rrt": { + "type": "text" + }, + "jwt-base64": { + "type": "text" + }, + "planetscale-password": { + "type": "text" + }, + "slack-config-refresh-token": { + "type": "text" + }, + "slack-webhook-url": { + "type": "text" + }, + "sources": { + "properties": { + "digitalocean-refresh-secret": { + "type": "text" + }, + "flutterwave-encryption-key": { + "type": "text" + }, + "gitter-access-key": { + "type": "text" + }, + "twitter-bearer-key": { + "type": "text" + } + } + } + } + }, + "additional_2": { + "properties": { + "adafruit-api-token": { + "type": "text" + }, + "builders": { + "properties": { + "additional_0": { + "properties": { + "dev_options": { + "properties": { + "age key token": { + "type": "text" + }, + "jfrog-identity-key": { + "type": "text" + }, + "jfrog-identity-token": { + "type": "text" + }, + "pulumi-api-token": { + "type": "text" + } + } + }, + "duffel-service-key": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "generate_for": { + "properties": { + "datadog-access-key": { + "type": "text" + }, + "dropbox-long-lived-service-secret": { + "type": "text" + }, + "finnhub-access-secret": { + "type": "text" + }, + "github-pat": { + "type": "text" + }, + "planetscale-api-token": { + "type": "text" + }, + "shopify-custom-access-key": { + "type": "text" + }, + "sidekiq-token": { + "type": "text" + }, + "slack-legacy-workspace-secret": { + "type": "text" + } + } + }, + "github-pat": { + "type": "text" + }, + "options": { + "properties": { + "adobe-client-token": { + "type": "text" + }, + "plaid-client-id": { + "type": "text" + }, + "slack-webhook-url": { + "type": "text" + }, + "sumologic-access-token": { + "type": "text" + } + } + }, + "planetscale-service-secret": { + "type": "text" + }, + "pulumi-api-token": { + "type": "text" + }, + "readme-api-token": { + "type": "text" + }, + "release_options": { + "properties": { + "linear-client-key": { + "type": "text" + }, + "postman-api-token": { + "type": "text" + }, + "shopify-private-app-access-key": { + "type": "text" + }, + "squarespace-access-key": { + "type": "text" + } + } + }, + "slack-legacy-bot-token": { + "type": "text" + }, + "sumologic-access-key": { + "type": "text" + }, + "twitter-access-secret": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "datadog-access-key": { + "type": "text" + }, + "dev_options": { + "properties": { + "frameio-service-key": { + "type": "text" + }, + "linkedin-client-id": { + "type": "text" + }, + "new-relic-user-api-token": { + "type": "text" + }, + "pulumi-service-token": { + "type": "text" + } + } + }, + "digitalocean-access-token": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "jfrog-api-token": { + "type": "text" + }, + "launchdarkly-access-secret": { + "type": "text" + }, + "options": { + "properties": { + "adobe-client-id": { + "type": "text" + }, + "finicity-client-secret": { + "type": "text" + }, + "gitlab-rrt": { + "type": "text" + }, + "rubygems-service-secret": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "authress-api-client-access-token": { + "type": "text" + }, + "freshbooks-access-secret": { + "type": "text" + }, + "plaid-client-id": { + "type": "text" + }, + "shopify-private-app-access-secret": { + "type": "text" + } + } + }, + "shopify-access-key": { + "type": "text" + }, + "shopify-custom-access-key": { + "type": "text" + }, + "shopify-private-app-access-token": { + "type": "text" + }, + "stripe-access-token": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "dev_options": { + "properties": { + "atlassian-service-secret": { + "type": "text" + }, + "droneci-access-key": { + "type": "text" + }, + "etsy-access-token": { + "type": "text" + }, + "fastly-api-token": { + "type": "text" + } + } + }, + "easypost-test-api-key": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "flutterwave-encryption-token": { + "type": "text" + }, + "mailgun-signing-key": { + "type": "text" + }, + "options": { + "properties": { + "asana-client-token": { + "type": "text" + }, + "github-app-token": { + "type": "text" + }, + "slack-webhook-url": { + "type": "text" + }, + "sumologic-access-token": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "asana-client-secret": { + "type": "text" + }, + "discord-client-key": { + "type": "text" + }, + "messagebird-client-id": { + "type": "text" + }, + "scalingo-api-secret": { + "type": "text" + } + } + }, + "shopify-custom-access-key": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "dev_options": { + "properties": { + "defined-networking-service-key": { + "type": "text" + }, + "etsy-access-secret": { + "type": "text" + }, + "openai-api-key": { + "type": "text" + }, + "twitter-access-secret": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "etsy-access-secret": { + "type": "text" + }, + "finicity-service-token": { + "type": "text" + }, + "generate_for": { + "properties": { + "age secret secret": { + "type": "text" + }, + "authress-service-client-access-key": { + "type": "text" + }, + "discord-service-token": { + "type": "text" + }, + "npm-access-key": { + "type": "text" + } + } + }, + "grafana-cloud-service-token": { + "type": "text" + }, + "options": { + "properties": { + "contentful-delivery-api-key": { + "type": "text" + }, + "github-app-key": { + "type": "text" + }, + "gocardless-service-secret": { + "type": "text" + }, + "shippo-service-token": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "alibaba-token-token": { + "type": "text" + }, + "github-refresh-key": { + "type": "text" + }, + "gocardless-api-token": { + "type": "text" + }, + "yandex-access-secret": { + "type": "text" + } + } + }, + "sendbird-access-id": { + "type": "text" + } + } + }, + "clojars-service-secret": { + "type": "text" + }, + "droneci-access-key": { + "type": "text" + }, + "easypost-test-api-token": { + "type": "text" + }, + "facebook": { + "type": "text" + }, + "linkedin-client-id": { + "type": "text" + }, + "messagebird-client-id": { + "type": "text" + }, + "slack-legacy-secret": { + "type": "text" + }, + "square-access-secret": { + "type": "text" + } + } + }, + "dependencies": { + "type": "text" + }, + "github-app-secret": { + "type": "text" + }, + "github-pat": { + "type": "text" + }, + "gitlab-pat": { + "type": "text" + }, + "jfrog-identity-secret": { + "type": "text" + }, + "mattermost-access-secret": { + "type": "text" + }, + "sidekiq-secret": { + "type": "text" + }, + "sources": { + "properties": { + "airtable-service-key": { + "type": "text" + }, + "flickr-access-token": { + "type": "text" + }, + "planetscale-api-key": { + "type": "text" + }, + "slack-user-key": { + "type": "text" + } + } + }, + "zendesk-secret-secret": { + "type": "text" + } + } + }, + "additional_3": { + "properties": { + "auto_apply_builders": { + "type": "boolean" + }, + "aws-access-token": { + "type": "text" + }, + "builders": { + "properties": { + "additional_0": { + "properties": { + "alibaba-access-key-id": { + "type": "text" + }, + "dev_options": { + "properties": { + "adobe-client-id": { + "type": "text" + }, + "fastly-api-secret": { + "type": "text" + }, + "grafana-cloud-service-token": { + "type": "text" + }, + "huggingface-access-token": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "generate_for": { + "properties": { + "algolia-service-token": { + "type": "text" + }, + "bittrex-access-token": { + "type": "text" + }, + "flickr-access-secret": { + "type": "text" + }, + "shippo-service-secret": { + "type": "text" + } + } + }, + "hubspot-service-token": { + "type": "text" + }, + "options": { + "properties": { + "adafruit-service-secret": { + "type": "text" + }, + "authress-api-client-access-secret": { + "type": "text" + }, + "digitalocean-pat": { + "type": "text" + }, + "openai-service-key": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "nytimes-access-token": { + "type": "text" + }, + "twitter-api-token": { + "type": "text" + }, + "zendesk-token-key": { + "type": "text" + } + } + }, + "twilio-api-token": { + "type": "text" + }, + "twitter-access-key": { + "type": "text" + } + } + }, + "additional_1": { + "properties": { + "dev_options": { + "properties": { + "beamer-service-key": { + "type": "text" + }, + "hubspot-service-token": { + "type": "text" + }, + "linear-client-token": { + "type": "text" + }, + "pulumi-api-secret": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "finnhub-access-secret": { + "type": "text" + }, + "jfrog-identity-key": { + "type": "text" + }, + "okta-access-key": { + "type": "text" + }, + "options": { + "properties": { + "adobe-client-secret": { + "type": "text" + }, + "gcp-api-secret": { + "type": "text" + }, + "mailgun-signing-key": { + "type": "text" + }, + "slack-legacy-workspace-secret": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "adobe-client-key": { + "type": "text" + }, + "dropbox-api-secret": { + "type": "text" + }, + "shippo-api-token": { + "type": "text" + }, + "slack-config-access-secret": { + "type": "text" + } + } + }, + "shopify-access-token": { + "type": "text" + } + } + }, + "additional_2": { + "properties": { + "bittrex-key-secret": { + "type": "text" + }, + "doppler-service-secret": { + "type": "text" + }, + "fastly-api-secret": { + "type": "text" + }, + "generate_for": { + "properties": { + "fastly-api-secret": { + "type": "text" + }, + "messagebird-client-id": { + "type": "text" + }, + "slack-config-access-key": { + "type": "text" + }, + "slack-webhook-url": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "adobe-client-id": { + "type": "text" + }, + "heroku-service-token": { + "type": "text" + }, + "pypi-upload-token": { + "type": "text" + }, + "vault-batch-token": { + "type": "text" + } + } + }, + "yandex-aws-access-key": { + "type": "text" + } + } + }, + "digitalocean-access-key": { + "type": "text" + }, + "finnhub-access-key": { + "type": "text" + }, + "flutterwave-encryption-secret": { + "type": "text" + }, + "github-oauth": { + "type": "text" + } + } + }, + "dependencies": { + "type": "text" + }, + "huggingface-organization-service-token": { + "type": "text" + }, + "slack-config-access-secret": { + "type": "text" + }, + "twitter-api-token": { + "type": "text" + } + } + }, + "additional_4": { + "properties": { + "auto_apply_builders": { + "type": "boolean" + }, + "builders": { + "properties": { + "additional_0": { + "properties": { + "asana-client-id": { + "type": "text" + }, + "dev_options": { + "properties": { + "bittrex-key-secret": { + "type": "text" + }, + "clojars-api-secret": { + "type": "text" + }, + "gitlab-rrt": { + "type": "text" + }, + "snyk-service-key": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "grafana-cloud-service-key": { + "type": "text" + }, + "new-relic-user-service-id": { + "type": "text" + }, + "options": { + "properties": { + "adafruit-service-secret": { + "type": "text" + }, + "databricks-service-token": { + "type": "text" + }, + "mailgun-pub-token": { + "type": "text" + }, + "slack-bot-secret": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "jfrog-api-key": { + "type": "text" + }, + "mailchimp-service-secret": { + "type": "text" + }, + "sidekiq-secret": { + "type": "text" + }, + "twitter-api-key": { + "type": "text" + } + } + }, + "slack-legacy-bot-secret": { + "type": "text" + } + } + }, + "age key secret": { + "type": "text" + }, + "gitlab-ptt": { + "type": "text" + }, + "linear-api-token": { + "type": "text" + }, + "new-relic-user-service-id": { + "type": "text" + } + } + }, + "dependencies": { + "type": "text" + }, + "droneci-access-key": { + "type": "text" + }, + "dropbox-short-lived-service-key": { + "type": "text" + }, + "microsoft-teams-webhook": { + "type": "text" + }, + "slack-bot-token": { + "type": "text" + } + } + }, + "additional_5": { + "properties": { + "auto_apply_builders": { + "type": "boolean" + }, + "aws-access-key": { + "type": "text" + }, + "builders": { + "properties": { + "additional_0": { + "properties": { + "confluent-secret-key": { + "type": "text" + }, + "dev_options": { + "properties": { + "etsy-access-key": { + "type": "text" + }, + "flickr-access-key": { + "type": "text" + }, + "new-relic-user-service-secret": { + "type": "text" + }, + "planetscale-password": { + "type": "text" + } + } + }, + "flutterwave-key-secret": { + "type": "text" + }, + "nytimes-access-token": { + "type": "text" + }, + "options": { + "properties": { + "finicity-api-key": { + "type": "text" + }, + "lob-pub-api-key": { + "type": "text" + }, + "mapbox-api-key": { + "type": "text" + }, + "planetscale-oauth-key": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "atlassian-service-token": { + "type": "text" + }, + "aws-access-secret": { + "type": "text" + }, + "sendbird-access-key": { + "type": "text" + }, + "yandex-service-key": { + "type": "text" + } + } + }, + "twitter-bearer-secret": { + "type": "text" + } + } + }, + "github-refresh-key": { + "type": "text" + }, + "gitlab-rrt": { + "type": "text" + }, + "new-relic-user-api-id": { + "type": "text" + }, + "sendbird-access-token": { + "type": "text" + } + } + }, + "dependencies": { + "type": "text" + }, + "flutterwave-key-key": { + "type": "text" + }, + "github-fine-grained-pat": { + "type": "text" + }, + "sources": { + "properties": { + "adafruit-api-token": { + "type": "text" + }, + "discord-api-token": { + "type": "text" + }, + "new-relic-user-service-id": { + "type": "text" + }, + "slack-legacy-bot-secret": { + "type": "text" + } + } + }, + "twitter-access-token": { + "type": "text" + } + } + }, + "additional_6": { + "properties": { + "adobe-client-id": { + "type": "text" + }, + "auto_apply_builders": { + "type": "boolean" + }, + "builders": { + "properties": { + "additional_0": { + "properties": { + "dev_options": { + "properties": { + "droneci-access-secret": { + "type": "text" + }, + "github-pat": { + "type": "text" + }, + "github-refresh-key": { + "type": "text" + }, + "new-relic-browser-service-secret": { + "type": "text" + } + } + }, + "dropbox-long-lived-service-token": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "generate_for": { + "properties": { + "gitlab-rrt": { + "type": "text" + }, + "mailgun-signing-secret": { + "type": "text" + }, + "slack-legacy-bot-secret": { + "type": "text" + }, + "yandex-aws-access-token": { + "type": "text" + } + } + }, + "options": { + "properties": { + "digitalocean-pat": { + "type": "text" + }, + "lob-pub-service-secret": { + "type": "text" + }, + "slack-app-secret": { + "type": "text" + }, + "twitter-bearer-secret": { + "type": "text" + } + } + }, + "release_options": { + "properties": { + "hashicorp-tf-api-key": { + "type": "text" + }, + "sendbird-access-id": { + "type": "text" + }, + "shopify-private-app-access-token": { + "type": "text" + }, + "stripe-access-key": { + "type": "text" + } + } + }, + "sendbird-access-token": { + "type": "text" + }, + "square-access-key": { + "type": "text" + }, + "square-access-secret": { + "type": "text" + } + } + }, + "clojars-service-secret": { + "type": "text" + }, + "digitalocean-pat": { + "type": "text" + }, + "kucoin-access-key": { + "type": "text" + }, + "new-relic-user-service-key": { + "type": "text" + } + } + }, + "linkedin-client-key": { + "type": "text" + }, + "shopify-private-app-access-key": { + "type": "text" + }, + "slack-config-refresh-token": { + "type": "text" + } + } + }, + "additional_7": { + "properties": { + "auto_apply_builders": { + "type": "boolean" + }, + "builders": { + "properties": { + "additional_0": { + "properties": { + "doppler-service-key": { + "type": "text" + }, + "doppler-service-token": { + "type": "text" + }, + "frameio-service-token": { + "type": "text" + }, + "generate_for": { + "properties": { + "frameio-api-secret": { + "type": "text" + }, + "plaid-client-id": { + "type": "text" + }, + "private-key": { + "type": "text" + }, + "sidekiq-key": { + "type": "text" + } + } + }, + "mattermost-access-key": { + "type": "text" + } + } + }, + "alibaba-secret-token": { + "type": "text" + }, + "easypost-service-key": { + "type": "text" + }, + "netlify-access-secret": { + "type": "text" + }, + "sidekiq-sensitive-url": { + "type": "text" + } + } + }, + "digitalocean-pat": { + "type": "text" + }, + "jwt-base64": { + "type": "text" + }, + "planetscale-password": { + "type": "text" + }, + "sendinblue-service-token": { + "type": "text" + } + } + }, + "bitbucket-client-id": { + "type": "text" + }, + "digitalocean-access-key": { + "type": "text" + }, + "discord-service-token": { + "type": "text" + }, + "mailchimp-api-key": { + "type": "text" + }, + "nytimes-access-key": { + "type": "text" + }, + "plaid-secret-key": { + "type": "text" + }, + "rapidapi-access-secret": { + "type": "text" + }, + "twitch-service-key": { + "type": "text" + } + } + }, + "telegram-bot-service-key": { + "type": "text" + }, + "telegram-bot-service-secret": { + "type": "text" + }, + "tidelift": { + "type": "text" + }, + "timeout": { + "type": "long" + }, + "travisci-access-secret": { + "type": "text" + }, + "travisci-access-token": { + "type": "text" + }, + "twitch-service-token": { + "type": "text" + }, + "update_at": { + "type": "date" + }, + "url": { + "type": "keyword" + }, + "webapp": { + "properties": { + "duffel-api-key": { + "type": "text" + }, + "hubspot-api-secret": { + "type": "text" + }, + "slack-config-refresh-key": { + "type": "text" + }, + "stripe-access-secret": { + "type": "text" + } + } + } + } + }, + "instance": { + "type": "keyword" + }, + "last": { + "type": "date" + }, + "nodeinfo": { + "properties": { + "$schema": { + "type": "text" + }, + "_destroyed": { + "type": "boolean" + }, + "approval_required": { + "type": "boolean" + }, + "configuration": { + "properties": { + "accounts": { + "properties": { + "max_featured_tags": { + "type": "long" + } + } + }, + "media_attachments": { + "properties": { + "image_matrix_limit": { + "type": "long" + }, + "image_size_limit": { + "type": "long" + }, + "supported_mime_types": { + "type": "text" + }, + "video_frame_rate_limit": { + "type": "long" + }, + "video_matrix_limit": { + "type": "long" + }, + "video_size_limit": { + "type": "long" + } + } + }, + "polls": { + "properties": { + "max_characters_per_option": { + "type": "long" + }, + "max_expiration": { + "type": "long" + }, + "max_options": { + "type": "long" + }, + "min_expiration": { + "type": "long" + } + } + }, + "statuses": { + "properties": { + "characters_reserved_per_url": { + "type": "long" + }, + "max_characters": { + "type": "long" + }, + "max_media_attachments": { + "type": "long" + } + } + } + } + }, + "contact_account": { + "properties": { + "acct": { + "type": "text" + }, + "avatar": { + "type": "text" + }, + "avatar_static": { + "type": "text" + }, + "bot": { + "type": "boolean" + }, + "created_at": { + "type": "date" + }, + "discoverable": { + "type": "boolean" + }, + "display_name": { + "type": "text" + }, + "fields": { + "properties": { + "name": { + "type": "text" + }, + "value": { + "type": "text" + }, + "verified_at": { + "type": "date" + } + } + }, + "followers_count": { + "type": "long" + }, + "following_count": { + "type": "long" + }, + "group": { + "type": "boolean" + }, + "header": { + "type": "text" + }, + "header_static": { + "type": "text" + }, + "hide_collections": { + "type": "boolean" + }, + "id": { + "type": "text" + }, + "indexable": { + "type": "boolean" + }, + "last_status_at": { + "type": "date" + }, + "locked": { + "type": "boolean" + }, + "noindex": { + "type": "boolean" + }, + "note": { + "type": "text" + }, + "roles": { + "properties": { + "color": { + "type": "text" + }, + "id": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "statuses_count": { + "type": "long" + }, + "uri": { + "type": "text" + }, + "url": { + "type": "text" + }, + "username": { + "type": "text" + } + } + }, + "description": { + "type": "text" + }, + "email": { + "type": "text" + }, + "error": { + "type": "text" + }, + "invites_enabled": { + "type": "boolean" + }, + "languages": { + "type": "text" + }, + "metadata": { + "properties": { + "accountActivationRequired": { + "type": "boolean" + }, + "chat_enabled": { + "type": "boolean" + }, + "disableBubbleTimeline": { + "type": "boolean" + }, + "disableGlobalTimeline": { + "type": "boolean" + }, + "disableLocalTimeline": { + "type": "boolean" + }, + "disableRecommendedTimeline": { + "type": "boolean" + }, + "disableRegistration": { + "type": "boolean" + }, + "donationUrl": { + "type": "text" + }, + "emailRequiredForSignup": { + "type": "boolean" + }, + "enableDiscordIntegration": { + "type": "boolean" + }, + "enableEmail": { + "type": "boolean" + }, + "enableFC": { + "type": "boolean" + }, + "enableGithubIntegration": { + "type": "boolean" + }, + "enableGlobalTimeline": { + "type": "boolean" + }, + "enableGuestTimeline": { + "type": "boolean" + }, + "enableHcaptcha": { + "type": "boolean" + }, + "enableLocalTimeline": { + "type": "boolean" + }, + "enableMcaptcha": { + "type": "boolean" + }, + "enableRecaptcha": { + "type": "boolean" + }, + "enableRecommendedTimeline": { + "type": "boolean" + }, + "enableServiceWorker": { + "type": "boolean" + }, + "enableTurnstile": { + "type": "boolean" + }, + "enableTwitterIntegration": { + "type": "boolean" + }, + "explicitContent": { + "type": "boolean" + }, + "features": { + "type": "text" + }, + "federatedTimelineAvailable": { + "type": "boolean" + }, + "federation": { + "properties": { + "enabled": { + "type": "boolean" + }, + "exclusions": { + "type": "boolean" + }, + "mrf_hashtag": { + "properties": { + "sensitive": { + "type": "text" + } + } + }, + "mrf_hellthread": { + "properties": { + "delist_threshold": { + "type": "long" + }, + "reject_threshold": { + "type": "long" + } + } + }, + "mrf_keyword": { + "properties": { + "reject": { + "type": "text" + }, + "replace": { + "properties": { + "pattern": { + "type": "text" + }, + "replacement": { + "type": "text" + } + } + } + } + }, + "mrf_object_age": { + "properties": { + "actions": { + "type": "text" + }, + "threshold": { + "type": "long" + } + } + }, + "mrf_policies": { + "type": "text" + }, + "mrf_simple": { + "properties": { + "federated_timeline_removal": { + "type": "text" + }, + "followers_only": { + "type": "text" + }, + "media_removal": { + "type": "text" + }, + "reject": { + "type": "text" + }, + "reject_deletes": { + "type": "text" + } + } + }, + "mrf_simple_info": { + "properties": { + "followers_only": { + "type": "object", + "enabled": false + }, + "media_removal": { + "type": "object", + "enabled": false + }, + "reject": { + "type": "object", + "enabled": false + }, + "reject_deletes": { + "type": "object", + "enabled": false + } + } + }, + "quarantined_instances_info": { + "properties": { + "quarantined_instances": { + "type": "object", + "enabled": false + } + } + }, + "rejected_instances": { + "type": "object", + "enabled": false + } + } + }, + "feedbackUrl": { + "type": "text" + }, + "fieldsLimits": { + "properties": { + "maxFields": { + "type": "long" + }, + "maxRemoteFields": { + "type": "long" + }, + "nameLength": { + "type": "long" + }, + "valueLength": { + "type": "long" + } + } + }, + "impressumUrl": { + "type": "text" + }, + "inquiryUrl": { + "type": "text" + }, + "invitesEnabled": { + "type": "boolean" + }, + "langs": { + "type": "text" + }, + "mailerEnabled": { + "type": "boolean" + }, + "maintainer": { + "properties": { + "email": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "maxAltTextLength": { + "type": "long" + }, + "maxCaptionTextLength": { + "type": "long" + }, + "maxCwLength": { + "type": "long" + }, + "maxNoteTextLength": { + "type": "long" + }, + "maxRemoteAltTextLength": { + "type": "long" + }, + "maxRemoteCwLength": { + "type": "long" + }, + "maxRemoteNoteTextLength": { + "type": "long" + }, + "nodeAdmins": { + "properties": { + "email": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "nodeDescription": { + "type": "text" + }, + "nodeName": { + "type": "text" + }, + "pollLimits": { + "properties": { + "max_expiration": { + "type": "long" + }, + "max_option_chars": { + "type": "long" + }, + "max_options": { + "type": "long" + }, + "min_expiration": { + "type": "long" + } + } + }, + "postEditing": { + "type": "boolean" + }, + "postFormats": { + "type": "text" + }, + "postImports": { + "type": "boolean" + }, + "post_formats": { + "type": "text" + }, + "privacyPolicyUrl": { + "type": "text" + }, + "private": { + "type": "boolean" + }, + "proxyAccountName": { + "type": "text" + }, + "publicTimelineVisibility": { + "properties": { + "bubble": { + "type": "boolean" + }, + "federated": { + "type": "boolean" + }, + "local": { + "type": "boolean" + } + } + }, + "repositoryUrl": { + "type": "text" + }, + "restrictedNicknames": { + "type": "text" + }, + "roles": { + "properties": { + "admin": { + "type": "text" + }, + "moderator": { + "type": "text" + } + } + }, + "searchFilters": { + "type": "boolean" + }, + "skipThreadContainment": { + "type": "boolean" + }, + "staffAccounts": { + "type": "text" + }, + "suggestions": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "themeColor": { + "type": "text" + }, + "tosUrl": { + "type": "text" + }, + "uploadLimits": { + "properties": { + "avatar": { + "type": "long" + }, + "background": { + "type": "long" + }, + "banner": { + "type": "long" + }, + "general": { + "type": "long" + } + } + }, + "upstream": { + "properties": { + "name": { + "type": "text" + }, + "version": { + "type": "text" + } + } + } + } + }, + "mulukhiya": { + "properties": { + "config": { + "properties": { + "controller": { + "type": "text" + }, + "status": { + "properties": { + "default_hashtag": { + "type": "text" + }, + "spoiler": { + "properties": { + "emoji": { + "type": "text" + }, + "shortcode": { + "type": "text" + } + } + } + } + } + } + }, + "package": { + "properties": { + "authors": { + "type": "text" + }, + "description": { + "type": "text" + }, + "email": { + "type": "text" + }, + "license": { + "type": "text" + }, + "url": { + "type": "text" + }, + "version": { + "type": "text" + } + } + } + } + }, + "openRegistrations": { + "type": "boolean" + }, + "operations": { + "properties": { + "com": { + "properties": { + "shinolabs": { + "properties": { + "api": { + "properties": { + "bite": { + "type": "text" + } + } + } + } + } + } + }, + "jetzt": { + "properties": { + "mia": { + "properties": { + "ns": { + "properties": { + "activitypub": { + "properties": { + "accept": { + "properties": { + "bite": { + "type": "text" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "protocols": { + "type": "text" + }, + "registrations": { + "type": "boolean" + }, + "rules": { + "properties": { + "hint": { + "type": "text" + }, + "id": { + "type": "text" + }, + "text": { + "type": "text" + } + } + }, + "services": { + "properties": { + "inbound": { + "type": "text" + }, + "outbound": { + "type": "text" + } + } + }, + "short_description": { + "type": "text" + }, + "software": { + "properties": { + "codename": { + "type": "text" + }, + "edition": { + "type": "text" + }, + "homepage": { + "type": "text" + }, + "name": { + "type": "text" + }, + "repository": { + "type": "text" + }, + "version": { + "type": "text" + } + } + }, + "stats": { + "properties": { + "domain_count": { + "type": "long" + }, + "status_count": { + "type": "long" + }, + "user_count": { + "type": "long" + } + } + }, + "thumbnail": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uri": { + "type": "text" + }, + "urls": { + "properties": { + "streaming_api": { + "type": "text" + } + } + }, + "usage": { + "properties": { + "localComments": { + "type": "long" + }, + "localPosts": { + "type": "long" + }, + "users": { + "properties": { + "activeHalfYear": { + "type": "long" + }, + "activeHalfyear": { + "type": "long" + }, + "activeMonth": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + }, + "version": { + "type": "text" + } + } + }, + "peers": { + "type": "text" + } + } + }, + "settings": { + "index.mapping.total_fields.limit": 10000, + "analysis": { + "analyzer": { + "default": { + "filter": [ + "lowercase", + "asciifolding", + "stop" + ], + "tokenizer": "whitespace" + } + } + } + } +} \ No newline at end of file diff --git a/back/index.js b/back/index.js new file mode 100644 index 0000000..ea89f3a --- /dev/null +++ b/back/index.js @@ -0,0 +1,3 @@ +const server = require('./lib/express') + +console.log('Server listening on ' + server.address().address + ':' + server.address().port) \ No newline at end of file diff --git a/back/lib/apex.js b/back/lib/apex.js new file mode 100644 index 0000000..7c84889 --- /dev/null +++ b/back/lib/apex.js @@ -0,0 +1,33 @@ +const ActivitypubExpress = require('activitypub-express'), + constant = require('../constant'), + routes = { + actor: '/u/:actor', + object: '/o/:id', + activity: '/s/:id', + inbox: '/u/:actor/inbox', + outbox: '/u/:actor/outbox', + followers: '/u/:actor/followers', + following: '/u/:actor/following', + liked: '/u/:actor/liked', + collections: '/u/:actor/c/:id', + blocked: '/u/:actor/blocked', + rejections: '/u/:actor/rejections', + rejected: '/u/:actor/rejected', + shares: '/s/:id/shares', + likes: '/s/:id/likes' + }, + apex = ActivitypubExpress({ + name: 'Fediblock Instance', + version: '1.0.0', + domain: constant.apexdomain, + actorParam: 'actor', + objectParam: 'id', + activityParam: 'id', + routes, + endpoints: { + proxyUrl: 'https://' + constant.apexdomain + '/proxy' + }, + itemsPerPage: 200 + }) + +module.exports = { routes, apex } \ No newline at end of file diff --git a/back/lib/apexcustom.js b/back/lib/apexcustom.js new file mode 100644 index 0000000..fd0837f --- /dev/null +++ b/back/lib/apexcustom.js @@ -0,0 +1,97 @@ +// custom side-effects for your app +module.exports = (app, apex, client) => { + const { sendFederatedMessage, requestPart } = require('./util'), + constant = require('../constant') + app.on('apex-outbox', msg => { + if (typeof msg === 'object' + && !Object.keys(msg).filter(prop => msg[prop] + && typeof msg[prop] === 'object').some(prop => !(msg[prop].hasOwnProperty('id') + && msg[prop].hasOwnProperty('type'))) + && msg.activity + && msg.activity.type) { + console.log(`New ${msg.activity.type} from ${msg.actor.id} to ${msg.recipient.id}`) + } else { + console.log(JSON.stringify(msg, '', 2)) + } + }) + app.on('apex-inbox', async msg => { + if (typeof msg === 'object' + && !Object.keys(msg).filter(prop => msg[prop] + && typeof msg[prop] === 'object').some(prop => !(msg[prop].hasOwnProperty('id') + && msg[prop].hasOwnProperty('type'))) + && msg.activity + && msg.activity.type) { + const type = msg.activity.type.toLowerCase() + if (type === 'follow' && msg.recipient.type.toLowerCase() === 'person') { + await apex.acceptFollow(msg.recipient, msg.activity) + const act = await apex.buildActivity('Accept', apex.utils.usernameToIRI(constant.nick), ['https://www.w3.org/ns/activitystreams#Public'].concat([msg.actor.id]), { + actor: msg.recipient, + object: msg.activity + }) + await apex.addToOutbox(await apex.store.getObject(apex.utils.usernameToIRI(constant.nick), true), act) + } + if (type === 'create' && msg.object.type.toLowerCase() === 'note' && msg.actor.preferredUsername && msg.object.content) { + const name = Array.isArray(msg.actor.preferredUsername) ? msg.actor.preferredUsername[0] : msg.actor.preferredUsername, + content = ('' + msg.object.content).replace(/<[^>]*>?/gm, '').split(' ').filter(token => !token.startsWith('@')) + if (msg.recipient.id === apex.utils.usernameToIRI(constant.nick)) { + if (content.length === 1) { + try { + const instance = content.join('').trim() + if (instance === 'stats') { + const statsres = await requestPart('https://' + constant.apexdomain + '/api/stats') + await sendFederatedMessage(constant.nick, null, `STATS\n +Statuses AVG: ${Math.round(statsres.status_avg)} +Statuses MAX: ${statsres.status_max} +Domain AVG: ${Math.round(statsres.domain_avg)} +Domain MAX: ${statsres.domain_max} +Users AVG: ${Math.round(statsres.user_avg)} +Users MAX: ${statsres.user_max} +Stats Instances: ${statsres.stats_filtered} +Total Instances: ${statsres.instance_count} +Users by Instance: ${(Math.round(statsres.user_avg) / statsres.instance_count).toFixed(2)} +Statuses by Domain: ${(Math.round(statsres.status_avg) / Math.round(statsres.domain_avg)).toFixed(2)} +Statuses by User: ${(Math.round(statsres.status_avg) / Math.round(statsres.user_avg)).toFixed(2)} +https://${constant.apexdomain}`, msg.actor.id, msg.object.id) + } else { + const json = await requestPart('https://' + constant.apexdomain + '/api/detail/' + instance) + if (json && json.blocks && Array.isArray(json.blocks) && json.blocks.length > 0) { + const res = await requestPart('https://' + constant.apexdomain + '/api/list/' + instance) + if (res && res.instances && Array.isArray(res.instances)) { + res.instances.map(async r => { + if (r.domain === instance) { + await sendFederatedMessage(constant.nick, null, 'Instance ' + instance + ' has ' + json.blocks.length + ' blocks\n' + + (r.api.title ? '\n' + + r.api.title + ' - ' + r.api.uri + '\n' + + (r.api.email ? 'Email: ' + r.api.email + '\n' : '') + + 'Registration: ' + (r.api.registrations ? 'open' : 'closed') + ' - Version: ' + r.api.version + '\n' + + (r.api.stats ? 'Users: ' + r.api.stats.user_count + ' - Statuses: ' + r.api.stats.status_count + ' - Domains: ' + r.api.stats.domain_count + '\n' : '') + + (r.api.description ? 'Description: ' + r.api.description + '\n' : '') : '') + + 'https://' + constant.apexdomain + '/#' + instance, msg.actor.id, msg.object.id) + } + }) + } else { + await sendFederatedMessage(constant.nick, null, 'Instance ' + instance + ' has ' + json.blocks.length + ' blocks - https://' + constant.apexdomain + '/#' + instance, msg.actor.id, msg.object.id) + } + } else { + await sendFederatedMessage(constant.nick, null, 'Instance ' + instance + ' not found, next try.', msg.actor.id, msg.object.id) + } + } + } catch (e) { + await sendFederatedMessage(constant.nick, null, e.message, msg.actor.id, msg.object.id) + // console.error(e) + } + } else { + await sendFederatedMessage(constant.nick, 'Hi ' + name, 'I know ' + (await client.count({ index: constant.index })).count + ' Federated Instances\nScanning ' + app.locals.server + ' instance with ' + app.locals.scantotal + ' peers\nScanned ' + app.locals.peers + ' peers from ' + app.locals.instances + ' instances, ' + app.locals.created + ' created, ' + app.locals.updated + ' updated\nhttps://' + constant.apexdomain, msg.actor.id, msg.object.id) + } + } + console.log(`New note from ${name} to ${msg.recipient.id}: ${content.join(' ')}`) + } else if (type !== 'delete') { + console.log(`New ${msg.activity.type} from ${msg.actor.id} to ${msg.recipient.id}`) + } else { + console.log(`New ${msg.activity.type} from ${msg.actor.id} to ${msg.recipient.id}`) + } + } else { + console.log(JSON.stringify(msg, '', 2)) + } + }) +} diff --git a/back/lib/api.js b/back/lib/api.js new file mode 100644 index 0000000..4318f7c --- /dev/null +++ b/back/lib/api.js @@ -0,0 +1,54 @@ +module.exports = (app, apex) => { + const constant = require('../constant'), + { readFile, writeFile } = require('fs'), + asyncHandler = require('express-async-handler') + app.get('/api/scan', (req, res) => { + res.header('Content-Type', 'text/event-stream') + res.header('Connection', 'keep-alive') + res.header('Cache-Control', 'no-cache') + res.header('X-Accel-Buffering', 'no') + res.write(Buffer.from('data: ...\n\n')) + req.app.locals.scan.on('data', data => { + res.write(Buffer.from(data)) + }) + res.setTimeout(20000, () => { + res.write(Buffer.from('data: ...\n\n')) + }) + }) + app.get('/api/served', (req, res, next) => { + readFile(__dirname + '/../served.txt', async (err, data) => { + if (err) { + next(err) + } else { + const num = parseInt(data.toString()) + 1 + writeFile(__dirname + '/../served.txt', num.toString(), 'utf8', (err) => { + if (err) { + next(err) + } else { + res.json({ served: num, lastscan: req.app.locals.scantotal, server: req.app.locals.server, instances: req.app.locals.instances, peers: req.app.locals.peers, created: req.app.locals.created, updated: req.app.locals.updated }) + } + }) + } + }) + }) + app.get('/api/outbox', asyncHandler(async (req, res, next) => { + const outbox = await apex.getOutbox(await apex.store.getObject(apex.utils.usernameToIRI(constant.nick), true), 'true'), + notes = outbox.orderedItems.filter(e => e.object + && e.object.length > 0 + && e.object[0].type === 'Note' + && e.object[0].content + && e.object[0].content[0].startsWith('

You')).slice(0, 20).map(e => ({ + content: e.object[0].content[0], + published: e.published[0] + })) + res.json(notes) + })) + app.get('/api/inbox', asyncHandler(async (req, res, next) => { + const actor = await apex.store.getObject(apex.utils.usernameToIRI(constant.nick), true) + actor._local = {} + actor._local.blockList = [] + const inbox = await apex.getInbox(actor, 'true'), + notes = inbox.orderedItems.filter(e => e.object && e.object.length > 0 && e.object[0].type === 'Note').slice(0, 20).map(e => e.object[0].content[0]) + res.json(notes) + })) +} diff --git a/back/lib/apiswagger.js b/back/lib/apiswagger.js new file mode 100644 index 0000000..d67409c --- /dev/null +++ b/back/lib/apiswagger.js @@ -0,0 +1,545 @@ +module.exports = (app, client) => { + const constant = require('../constant'), + zlib = require('zlib'), + asyncHandler = require('express-async-handler'), + { param, validationResult } = require('express-validator') + /** + * @swagger + * /api/stats: + * get: + * summary: Retrieve stats of instances. + * description: Retrieve stats of total instances. + * responses: + * 200: + * description: A object with stats parameters. + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: object + * properties: + * instance_count: + * type: integer + * description: Count of instances. + * example: 0 + * status_avg: + * type: float + * description: Average statuses of total instances. + * example: 0 + * status_max: + * type: integer + * description: Max of total statuses instances. + * example: 0 + * user_avg: + * type: float + * description: Average users of total instances. + * example: 0 + * user_max: + * type: integer + * description: Max of total users instances. + * example: 0 + * domain_avg: + * type: float + * description: Number of average domains instances. + * example: 0 + * domain_max: + * type: integer + * description: Number of max domains instances. + * example: 0 + * stats_filtered: + * type: integer + * description: Number of instances stats. + * example: 0 + */ + app.get('/api/stats', asyncHandler(async (req, res) => { + const result = await client.search({ + index: constant.index, + body: { + size: 0, + aggs: { + instance_count: { + value_count: { + field: '_id' + } + }, + stats_filtered: { + filter: { + exists: { + field: 'api.stats' + } + } + }, + user_avg: { + avg: { + field: 'api.stats.user_count' + } + }, + user_max: { + max: { + field: 'api.stats.user_count' + } + }, + domain_avg: { + avg: { + field: 'api.stats.domain_count' + } + }, + domain_max: { + max: { + field: 'api.stats.domain_count' + } + }, + status_avg: { + avg: { + field: 'api.stats.status_count' + } + }, + status_max: { + max: { + field: 'api.stats.status_count' + } + } + + } + } + }) + res.json(Object.keys(result.aggregations).reduce((prev, curr) => ({ + ...prev, [curr]: result.aggregations[curr][Object.keys(result.aggregations[curr])[0]] + }), {})) + })) + /** + * @swagger + * /api/count: + * get: + * summary: Retrieve count of instances. + * description: Retrieve a number of total instances. + * responses: + * 200: + * description: A object with count parameter. + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: object + * properties: + * count: + * type: integer + * description: Number of instances. + * example: 0 + */ + app.get('/api/count', asyncHandler(async (req, res) => { + res.json({ count: (await client.count({ index: constant.index })).count }) + })) + /** + * @swagger + * /api/ranking: + * get: + * summary: Retrieve one ranking of instances. + * description: Retrieve a top ten ranking of total fediblock instances. + * responses: + * 200: + * description: A object with count parameter. + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: object + * properties: + * domain: + * type: string + * description: Domain of the instance. + * example: "mastodon.social" + * count: + * type: integer + * description: Number of fediblocks. + * example: 0 + */ + app.get('/api/ranking', asyncHandler(async (req, res) => { + const result = await client.search({ + index: constant.index, + body: { + size: 0, + aggs: { + blocks: { + nested: { + path: 'blocks' + }, + aggs: { + ranking: { + terms: { + field: 'blocks.domain', + size: 100 + } + } + } + } + } + } + }) + const ranking = result.aggregations?.blocks?.ranking?.buckets + res.json(ranking.map(r => ({ domain: r.key, count: r.doc_count }))) + })) + /** + * @swagger + * /api/list/{instance}: + * get: + * summary: Search result array of intances. + * description: Retrieve a result array of instances matching search input. + * parameters: + * - in: path + * name: instance + * required: true + * description: String for search instance + * schema: + * type: string + * responses: + * 200: + * description: A list of instances. + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * items: + * type: object + * properties: + * instances: + * type: array + * items: + * type: data + * description: List of instances. + * example: "mastodon.social" + * suggests: + * type: array + * items: + * type: data + * description: Suggest of the instance. + * example: "mastodon.social" + */ + app.get('/api/list/:instance', param('instance').notEmpty().trim().escape(), asyncHandler(async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ + errors: errors.array() + }) + } + if (req.params.instance && req.params.instance.length > 0) { + const result = await client.search({ + index: constant.index, + size: 10, + query: { + wildcard: { + instance: { + value: `*${req.params.instance}*` + } + }, + }, + suggest: { + suggests: { + text: req.params.instance, + term: { + field: 'instance', + size: 3, + sort: 'score', + suggest_mode: 'always', + max_edits: 2, + min_word_length: 1 + } + } + } + }) + const instances = result.hits?.hits?.length > 0 ? result.hits.hits : [], + suggests = result.suggest.suggests && result.suggest.suggests.length > 0 && result.suggest.suggests[0].options.length > 0 ? result.suggest.suggests[0].options : [] + res.json({ + instances: instances.map(instance => ({ + domain: instance._source.instance, + api: instance._source.api ? instance._source.api : null, + blocks: instance._source.blocks && instance._source.blocks.length > 0 ? instance._source.blocks.length : null, + last: instance._source.last ? instance._source.last : null, + nodeinfo: instance._source.nodeinfo ? true : false + })), suggests: suggests.map(instance => instance.text) + }) + } else { + res.status(404).end() + } + })) + /** + * @swagger + * /api/detail/{instance}: + * get: + * summary: Search result array of fediblocks intances. + * description: Retrieve a result array of fediblock instances matching search input. + * parameters: + * - in: path + * name: instance + * required: true + * description: String to detail of the instance + * schema: + * type: string + * responses: + * 200: + * description: A list of instances. + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * items: + * type: object + * properties: + * domain: + * type: string + * description: Domain of the block instance. + * example: "mastodon.social" + * comment: + * type: string + * description: Comment of the block instance. + * example: "mastodon.social" + * severity: + * type: string + * description: Severity of the block instance. + * example: "mastodon.social" + * + */ + app.get('/api/detail/:instance', param('instance').notEmpty().trim().escape(), asyncHandler(async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ + errors: errors.array() + }) + } + if (req.params.instance && req.params.instance.length > 0) { + const result = await client.search({ + index: constant.index, + size: 1, + query: { + term: { + instance: req.params.instance + } + } + }) + const instances = result.hits?.hits?.length > 0 ? result.hits.hits : [] + res.json(instances.length > 0 && instances[0]._source.blocks && instances[0]._source.blocks.length > 0 ? + { + blocks: instances[0]._source.blocks.map(instance => ({ domain: instance.domain, comment: instance.comment, severity: instance.severity })), + last: instances[0]._source.last, + instance: instances[0]._source.instance, + nodeinfo: instances[0]._source.nodeinfo, + api: instances[0]._source.api, + took: result.took + } : []) + } else { + res.status(404).end() + } + })) + /** + * @swagger + * /api/detail_api/{instance}: + * get: + * summary: Search result of detail's api intance. + * description: Retrieve a result of detail's api instance. + * parameters: + * - in: path + * name: instance + * required: true + * description: String to detail of the instance + * schema: + * type: string + * responses: + * 200: + * description: Detail of the api instance. + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: object + */ + app.get('/api/detail_api/:instance', param('instance').notEmpty().trim().escape(), asyncHandler(async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ + errors: errors.array() + }) + } + if (req.params.instance && req.params.instance.length > 0) { + const result = await client.search({ + index: constant.index, + size: 1, + query: { + term: { + instance: req.params.instance + } + } + }) + const instances = result.hits?.hits?.length > 0 ? result.hits.hits : [] + res.json(instances.length > 0 ? instances[0]._source.api : {}) + } else { + res.status(404).end() + } + })) + /** + * @swagger + * /api/detail_nodeinfo/{instance}: + * get: + * summary: Search result of detail's nodeinfo intance. + * description: Retrieve a result of detail's nodeinfo instance. + * parameters: + * - in: path + * name: instance + * required: true + * description: String to detail of the instance + * schema: + * type: string + * responses: + * 200: + * description: Detail of the nodeinfo instance. + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: object + */ + app.get('/api/detail_nodeinfo/:instance', param('instance').notEmpty().trim().escape(), asyncHandler(async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ + errors: errors.array() + }) + } + if (req.params.instance && req.params.instance.length > 0) { + const result = await client.search({ + index: constant.index, + size: 1, + query: { + term: { + instance: req.params.instance + } + } + }) + const instances = result.hits?.hits?.length > 0 ? result.hits.hits : [] + res.json(instances.length > 0 ? instances[0]._source.nodeinfo : {}) + } else { + res.status(404).end() + } + })) + /** + * @swagger + * /api/block_count/{instance}: + * get: + * summary: Retrieve count of fediblocked instances. + * description: Retrieve a number of total fediblocked instances. + * parameters: + * - in: path + * name: instance + * required: true + * description: String to search fediblocks of the instance + * schema: + * type: string + * responses: + * 200: + * description: A object with block_count parameter. + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: object + * properties: + * block_count: + * type: integer + * description: Number of fediblock instances. + * example: 0 + * instances: + * type: array + * items: + * type: object + * properties: + * instance: + * type: string + * description: Instance of the block. + * example: mastodon.social + * comment: + * type: string + * description: Comment about the block. + * example: spam + */ + app.get('/api/block_count/:instance', param('instance').notEmpty().trim().escape(), asyncHandler(async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ + errors: errors.array() + }) + } + if (req.params.instance && req.params.instance.length > 0) { + const result = await client.search({ + index: constant.index, + size: 9999, + query: { + nested: { + path: 'blocks', + query: { + term: { + 'blocks.domain': req.params.instance + } + } + } + } + }) + const instances = result.hits?.hits?.length > 0 ? result.hits.hits : [], + instancescomment = instances.map(i => ({ + instance: i._source.instance, comment: i._source.blocks.find(block => block.domain === req.params.instance).comment + })) + res.json({ + block_count: instances.length, + instances: instancescomment, + took: result.took + }) + } else { + res.status(404).end() + } + })) + /** + * @swagger + * /api/download_index: + * get: + * summary: Retrieve all content of ElasticSearch index. + * description: Retrieve all content of ElasticSearch index. + * responses: + * 200: + * description: A file compressed with gzip. + * content: + * application/gzip: + */ + app.get('/api/download_index', asyncHandler(async (req, res) => { + try { + res.setHeader('Content-Type', 'application/gzip') + res.setHeader('Content-disposition', 'attachment; filename=fediblock-index.json.gz') + const result = await client.search({ + index: constant.index, + size: 9999, + query: { + match_all: {} + } + }, { asStream: true, meta: false }) + result.pipe(zlib.createGzip()).pipe(res) + } catch (e) { + console.error(e) + res.status(404).end() + } + })) +} diff --git a/back/lib/express.js b/back/lib/express.js new file mode 100644 index 0000000..28262b8 --- /dev/null +++ b/back/lib/express.js @@ -0,0 +1,158 @@ +const http = require('http'), + express = require('express'), + rateLimit = require('express-rate-limit'), + app = express(), + events = require('events'), + constant = require('../constant'), + logger = require('./logger'), + { routes, apex } = require('./apex'), + apexcustom = require('./apexcustom'), + apiswagger = require('./apiswagger'), + api = require('./api'), + swagger = require('./swagger'), + fediblock = require('./fediblock'), + taskdeletedup = require('./taskdeletedup'), + { generateKeyPairSync } = require('crypto'), + { MongoClient } = require('mongodb'), + mongoclient = new MongoClient(constant.dburl), + { Client } = require('@elastic/elasticsearch'), + client = new Client({ + node: constant.elasticnode, + pingTimeout: 10000, + requestTimeout: 60000, + retryOnTimeout: true, + maxRetries: 3 + }), + register = async () => { + const admin = await apex.store.getObject(apex.utils.usernameToIRI('admin'), true) + if (!admin) { + const adminaccount = await apex.createActor('admin', 'Fediblock Admin', 'Fediblock Admin - https://' + constant.apexdomain, + { type: 'Image', mediaType: 'image/png', url: constant.icon }, 'Service'), + keys = generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem' + } + }) + adminaccount.publicKey[0].publicKeyPem[0] = keys.publicKey + adminaccount._meta.privateKey = keys.privateKey + await apex.store.saveObject(adminaccount) + const bot = await apex.createActor(constant.nick, 'Fediblock Bot', 'Fediblock #Bot - https://' + constant.apexdomain, + { type: 'Image', mediaType: 'image/png', url: constant.icon }, 'Person'), + keysbot = generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem' + } + }) + bot.publicKey[0].publicKeyPem[0] = keysbot.publicKey + bot._meta.privateKey = keysbot.privateKey + await apex.store.saveObject(bot) + apex.systemUser = adminaccount + apex.store.setup(adminaccount) + } else { + apex.systemUser = admin + } + }, + connect = () => { + mongoclient.connect() + .then(async () => { + const exists = await client.indices.exists({ index: constant.index }) + if (!exists) { + await client.indices.create({ + index: constant.index, + body: require('../fediblock-mapping.json') + }) + } + apex.store.db = mongoclient.db(constant.dbname) + return apex.store.db + }) + .then(async () => { + try { + await register() + taskdeletedup(client) + await apex.startDelivery() + await fediblock(client, app) + } catch (e) { + console.error(e) + } + }) + }, + server = http.createServer(app).listen(4000, () => { + connect() + }) + +app.locals.scan = new events.EventEmitter() +app.locals.scannum = 0 +app.locals.scantotal = 0 +app.locals.server = '' +app.locals.peers = 0 +app.locals.instances = 0 +app.locals.created = 0 +app.locals.updated = 0 +app.disable('x-powered-by') +app.set('json spaces', 2) +app.set('trust proxy', 1) +app.use(logger) +app.use(rateLimit({ + windowMs: 1 * 60 * 1000, // 1 minutes + limit: 120, // each IP can make up to 120 requests per `windowsMs` (5 minutes) + standardHeaders: true, // add the `RateLimit-*` headers to the response + legacyHeaders: false, + delayAfter: 30, // allow 30 requests per `windowMs` (5 minutes) without slowing them down + delayMs: (hits) => hits * 200, // add 200 ms of delay to every request after the 10th + maxDelayMs: 5000 +})) +app.use( + express.json({ type: apex.consts.jsonldTypes }), + express.urlencoded({ extended: true }), + apex +) +app.route(routes.inbox) + .get(apex.net.inbox.get) + .post(apex.net.inbox.post) +app.route(routes.outbox) + .get(apex.net.outbox.get) + .post(apex.net.outbox.post) +app.get(routes.actor, apex.net.actor.get) +app.get(routes.followers, apex.net.followers.get) +app.get(routes.following, apex.net.following.get) +app.get(routes.liked, apex.net.liked.get) +app.get(routes.object, apex.net.object.get) +app.get(routes.activity, apex.net.activityStream.get) +app.get(routes.shares, apex.net.shares.get) +app.get(routes.likes, apex.net.likes.get) +app.get('/.well-known/webfinger', apex.net.webfinger.get) +app.get('/.well-known/nodeinfo', apex.net.nodeInfoLocation.get) +app.get('/nodeinfo/:version', apex.net.nodeInfo.get) +app.post('/proxy', apex.net.proxy.post) +apexcustom(app, apex, client) +api(app, apex, client) +apiswagger(app, client) +swagger(app) +app.get('/u/' + constant.nick, (req, res) => { + res.redirect('/api/inbox') +}) +app.get(Object.keys(routes).map(route => routes[route]), (req, res) => { + res.redirect('/') +}) +app.use(express.static(__dirname + '/../../front/build')) + .use((err, req, res, next) => { + if (res.headersSent) { + console.error(err.stack) + return next(err) + } + res.status(500).end('Error') + }) + +module.exports = server \ No newline at end of file diff --git a/back/lib/fediblock.js b/back/lib/fediblock.js new file mode 100644 index 0000000..fd5f2ae --- /dev/null +++ b/back/lib/fediblock.js @@ -0,0 +1,211 @@ +module.exports = async (client, app) => { + const { sendFederatedMessage, requestPart } = require('./util'), + constant = require('../constant'), + schedule = require('node-schedule'), + { PromisePool } = require('@supercharge/promise-pool'), + workers = constant.workers, + threads = constant.threads, + getAccount = api => { + if (api && api.contact_account.acct) { + const acct = api.contact_account.acct.split('@') + if (acct.length > 1) { + return `https://${acct[1]}/users/${acct[0]}` + } else { + return `https://${api.uri.replace(/https?:\/\//, '')}/users/${acct.join('')}` + } + } else { + return '' + } + }, + scanInstance = async instance => { + const json = await requestPart(`https://${instance}/api/v1/instance/domain_blocks`) + if (Array.isArray(json) && json.length > 0) { + const result = await client.search({ + index: constant.index, + size: 1, + query: { + term: { + instance: instance + } + } + }), + instancelocated = result.hits && result.hits.hits ? result.hits.hits : [], + blocks = json.map(block => { + if (block.comment && block.comment.length > 8190) { + block.comment = block.comment.slice(0, 8190) + } + return block + }), + [api, nodeinfo, peers] = await Promise.all([ + requestPart(`https://${instance}/api/v1/instance`), + requestPart(`https://${instance}/nodeinfo/2.0`), + requestPart(`https://${instance}/api/v1/instance/peers`) + ]) + if (result && result.hits) { + if (instancelocated.length === 0) { + await client.index({ + index: constant.index, + body: { + instance, + api, + nodeinfo, + blocks, + peers, + last: new Date() + } + }) + app.locals.created++ + return sendFederatedMessage(constant.nick, 'New Fediblock Instance', `Fediblock Instance ${instance} with ${json.length} blocks - https://${constant.apexdomain}#${instance}`, getAccount(api)) + } else { + const elasticinstance = instancelocated[0]._source.blocks || [] + if (Array.isArray(elasticinstance)) { + if (json.length !== elasticinstance.length + || (instancelocated[0]._source.last && instancelocated[0]._source.last < new Date(Date.now() - 2678400000)) + || !instancelocated[0]._source.api + || !instancelocated[0]._source.nodeinfo + || !instancelocated[0]._source.peers) { + await client.update({ + index: constant.index, + id: instancelocated[0]._id, + doc: { + api: api ? api : instancelocated[0]._source.api, + nodeinfo: nodeinfo ? nodeinfo : instancelocated[0]._source.nodeinfo, + blocks: blocks && blocks.length > 0 ? blocks : elasticinstance, + peers: peers && peers.length > 0 ? peers : instancelocated[0]._source.peers, + last: new Date() + }, + doc_as_upsert: true + }) + app.locals.updated++ + if (instancelocated[0]._source.api && instancelocated[0]._source.api.uri && instancelocated[0]._source.api.contact_account && instancelocated[0]._source.api.contact_account.acct) { + const difference = blocks.filter(block => block.domain && block.domain.trim().length > 0 && !elasticinstance.some(instance => block.domain === instance.domain)) + if (difference.length > 0 && difference.length < 20) { + return sendFederatedMessage(constant.nick, 'Detected #Fediblock by Fediblock Instance', `You blocked new instances: ${difference.map(d => d.domain).join(', ')} - https://${constant.apexdomain}#${instance}`, getAccount(instancelocated[0]._source.api)) + } + } + } + } + } + } + } + }, + scanPart = async instancesall => PromisePool + .withConcurrency(threads) + .for(instancesall) + .process(async instance => { + app.locals.scan.emit('data', 'data: ' + (app.locals.scannum > 0 ? '(' + app.locals.scannum-- + '): ' + instance : ': ' + instance) + '\n\n') + app.locals.peers++ + return scanInstance(instance) + }), + scanIndex = async (server, instancesall) => { + const [api, nodeinfo, blocks] = await Promise.all([ + requestPart(`https://${server}/api/v1/instance`), + requestPart(`https://${server}/nodeinfo/2.0`), + requestPart(`https://${server}/api/v1/instance/domain_blocks`) + ]) + if (api && typeof api === 'object' && api.version) { + const result = await client.search({ + index: constant.index, + size: 1, + query: { + term: { + instance: server + } + } + }), + instancelocated = result.hits && result.hits.hits ? result.hits.hits : [] + if (result && result.hits) { + if (instancelocated.length === 0) { + await client.index({ + index: constant.index, + body: { + instance: server, + peers: instancesall, + api, + nodeinfo, + blocks: blocks && blocks.length > 0 ? blocks : [], + last: new Date() + } + }) + app.locals.created++ + } else { + const elasticinstance = instancelocated[0]._source.peers || [] + if (Array.isArray(elasticinstance) && Array.isArray(instancesall) && instancesall.length > 0) { + if (instancesall.length !== elasticinstance.filter(i => !constant.filterdomains.some(d => i.endsWith(d))).length) { + await client.update({ + index: constant.index, + id: instancelocated[0]._id, + doc: { + peers: instancesall.length > 0 ? instancesall : elasticinstance, + api: api ? api : instancelocated[0]._source.api, + nodeinfo: nodeinfo ? nodeinfo : instancelocated[0]._source.nodeinfo, + blocks: blocks && blocks.length > 0 ? blocks : instancelocated[0]._source.blocks, + last: new Date() + }, + doc_as_upsert: true + }) + app.locals.updated++ + } + } + } + } + } + }, + scanReturn = async () => { + app.locals.scannum = 0 + if (app.locals.servers?.length > 0) { + await scan(app.locals.servers.shift()) + } else { + await scan(constant.initialscan) + } + }, + scan = async server => { + if (server) { + try { + console.log(server) + app.locals.scan.emit('data', 'data: ' + server + ' peers...\n\n') + const instances = await requestPart(`https://${server}/api/v1/instance/peers`), + instancesall = Array.isArray(instances) && instances.length > 0 + ? instances.filter(i => !constant.filterdomains.some(d => i.endsWith(d))) + : [], + instancessorted = instancesall.sort((a, b) => a < b ? -1 : a > b ? 1 : 0), + parts = [], + split = workers, + chunkSize = Math.ceil(instancessorted.length / split) + if (instancesall.length > 0) { + await scanIndex(server, instancesall) + if (!app.locals.servers || app.locals.servers.length === 0) { + app.locals.servers = instancesall.sort(() => Math.random() - 0.5) + } + if (server === constant.initialscan) { + return scan(app.locals.servers.shift()) + } + app.locals.scannum = instancessorted.length + app.locals.scantotal = instancessorted.length + app.locals.server = server + app.locals.instances++ + for (let i = 0; i < instancessorted.length; i += chunkSize) { + const chunk = instancessorted.slice(i, i + chunkSize) + parts.push(chunk) + } + await PromisePool + .withConcurrency(workers) + .for(parts) + .process(scanPart) + await scanReturn() + } else { + await scanReturn() + } + } catch (e) { + // console.error(e) + await scanReturn() + } + } else { + await scanReturn() + } + }, + job = schedule.scheduleJob('0 ' + constant.schedule + ' * * *', async () => { + await sendFederatedMessage(constant.nick, null, 'Scanning ' + app.locals.server + ' instance with ' + app.locals.scantotal + ' peers\nScanned ' + app.locals.peers + ' peers from ' + app.locals.instances + ' instances, ' + app.locals.created + ' created, ' + app.locals.updated + ' updated\nhttps://' + constant.apexdomain) + }) + return scanReturn() +} diff --git a/back/lib/logger.js b/back/lib/logger.js new file mode 100644 index 0000000..b695f25 --- /dev/null +++ b/back/lib/logger.js @@ -0,0 +1,6 @@ +const morgan = require('morgan'), + rfs = require('rotating-file-stream'), + accessLogStream = rfs.createStream('access.log', { interval: '1d', path: __dirname + '/../logs' }), + logger = morgan('combined', { stream: accessLogStream }) + +module.exports = logger diff --git a/back/lib/swagger.js b/back/lib/swagger.js new file mode 100644 index 0000000..85218a3 --- /dev/null +++ b/back/lib/swagger.js @@ -0,0 +1,25 @@ +module.exports = app => { + const swaggerJsdoc = require('swagger-jsdoc'), + swaggerUi = require('swagger-ui-express'), + options = { + swaggerOptions: { + withCredentials: false + }, + swaggerDefinition: { + restapi: '3.1.0', + info: { + title: 'Fediblock Instance API', + version: '1.0.0', + description: '#Fediblock Instance REST API', + }, + servers: [ + { + url: 'http://localhost:3000', + }, + ], + }, + apis: ['**/apiswagger.js'], + }, + specs = swaggerJsdoc(options) + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs, { explorer: false })) +} diff --git a/back/lib/taskdeletedup.js b/back/lib/taskdeletedup.js new file mode 100644 index 0000000..a2a4092 --- /dev/null +++ b/back/lib/taskdeletedup.js @@ -0,0 +1,69 @@ +module.exports = client => { + const constant = require('../constant'), + schedule = require('node-schedule'), + { pick } = require('stream-json/filters/Pick'), + { parser } = require('stream-json'), + { streamArray } = require('stream-json/streamers/StreamArray'), + { chain } = require('stream-chain'), + size = 500, + deleteDup = async () => { + const count = { deleted: 0, total: 0, current: 0 } + let lastsort = undefined, last = undefined + await searchDup(count, lastsort, last) + }, + searchDup = async (count, lastsort, last) => { + count.current = 0 + const result = await client.search({ + index: constant.index, + size: size, + body: { + query: { + match_all: {} + }, + sort: [{ + "instance": { + "order": "asc" + }, + "last": { + "order": "desc", + "numeric_type": "date_nanos", + "format": "strict_date_optional_time_nanos" + } + }], + search_after: lastsort + } + }, { asStream: true, meta: false }), + pipeline = chain([ + parser(), + pick({ filter: 'hits.hits' }), + streamArray(), + data => data.value + ]) + pipeline.on('data', async data => { + count.current++ + if (last && last.instance === data._source.instance) { + await client.delete({ index: constant.index, id: data._id }) + count.deleted++ + console.log('deleted ' + data._id + ': ' + data._source.instance) + } + else { + last = data._source + } + if (count.current === size) { + lastsort = data.sort + } + }) + pipeline.on('end', async () => { + count.total += count.current + if (count.current === size) { + await searchDup(count, lastsort, last) + } else { + console.log('Index: ' + constant.index + ' - Total: ' + count.total + ' - Deleted: ' + count.deleted) + } + }) + result.pipe(pipeline) + }, + job = schedule.scheduleJob('0 ' + constant.taskdeletedup + ' * * *', async () => { + await deleteDup() + }) +} diff --git a/back/lib/util.js b/back/lib/util.js new file mode 100644 index 0000000..8d535ce --- /dev/null +++ b/back/lib/util.js @@ -0,0 +1,83 @@ +const constant = require("../constant"), + { apex } = require('./apex'), + https = require('node:https'), + json = 'application/json', + urlToId = url => { + try { + if (typeof new URL(url) === 'object' && url.split('/').length === 5 && url.split('/')[3].length > 0) { + return '@' + url.split('/')[4] + '@' + url.split('/')[2] + } else { + return url + } + } catch (e) { + return url + } + }, + sendFederatedMessage = async (id, summary, message, recipient, reply) => { + const users = message.split(' ').filter(token => token.startsWith('@') && token.split('@').length === 3) + .map(token => `https://${token.split('@')[2]}/users/${token.split('@')[1]}`), + mentions = message.split(' ').filter(token => token.startsWith('@') && token.split('@').length === 3) + .map(token => { return { type: 'Mention', href: `https://${token.split('@')[2]}/users/${token.split('@')[1]}`, name: `@${token.split('@')[1]}` } }), + hashtags = message.split(' ').filter(token => token.startsWith('#')) + .map(token => { return { id: `https://${constant.apexdomain}/tags/${token.split('#')[1]}`, name: token } }), + images = message.split(' ').filter(token => token.match(/^https?:\/\//i) && token.match(/\.(jpg|png|gif|jpeg|ppm)$/i)) + .map(token => { return { type: 'Image', url: token } }), + audio = message.split(' ').filter(token => token.match(/^https?:\/\//i) && token.match(/\.(mp3|wav|flac)$/i)) + .map(token => { return { type: 'Audio', url: token } }), + video = message.split(' ').filter(token => token.match(/^https?:\/\//i) && token.match(/\.(mp4|flv|avi)$/i)) + .map(token => { return { type: 'Video', url: token } }), + links = message.split(' ').filter(token => token.match(/^https?:\/\//i) && !images.concat(audio).concat(video) + .some(link => link.url === token)).map(token => { return { type: 'Link', href: token } }), + allusers = users.concat([recipient || (await apex.store.getObject(apex.utils.usernameToIRI(id), true)).followers[0]]), + act = await apex.buildActivity('Create', apex.utils.usernameToIRI(id), ['https://www.w3.org/ns/activitystreams#Public'].concat(allusers), { + object: { + type: 'Note', + name: `Fediblock Instance`, + summary: summary || null, + content: `

${message.replace(/(\b(https?|ftp|file):\/\/([-A-Z0-9+&@#%?=~_|!:,.;]*)([-A-Z0-9+&@#%?/=~_|!:,.;]*))/ig, + '$3').replace(/\s#(\S+)/g, '#$1').replace(/\r?\n/g, '
')}

`, + tag: hashtags.concat(mentions), + attachment: images.concat(links).concat(audio).concat(video), + inReplyTo: reply || null + } + }) + act.object[0].id = act.id + await apex.addToOutbox(await apex.store.getObject(apex.utils.usernameToIRI(id), true), act) + }, + requestPart = uri => new Promise((resolve, reject) => { + try { + const data = [], + req = https.request(uri, { + headers: { + 'User-Agent': constant.agent, + 'Content-Type': json + }, + signal: AbortSignal.timeout(constant.abort_timeout), + }, res => { + res.setEncoding('utf8') + res.headers['Content-Type'] = json + res.on('data', chunk => { + data.push(chunk) + }) + res.on('error', error => { + reject(error) + }) + res.on('end', () => { + try { + resolve(JSON.parse(data.join(''))) + } catch (error) { + reject(error) + } + }) + }) + req.on('error', error => { + reject(error) + }) + req.end() + } catch (error) { + reject(error) + } + }) + +module.exports = { urlToId, sendFederatedMessage, requestPart } + diff --git a/back/package.json b/back/package.json new file mode 100644 index 0000000..ffce9c1 --- /dev/null +++ b/back/package.json @@ -0,0 +1,35 @@ +{ + "name": "fediblock-instance", + "version": "1.0.0", + "description": "Fediblock Instance", + "author": "ale", + "repository": { + "type": "git", + "url": "https://git.manalejandro.com/ale/fediblock-instance" + }, + "license": "MIT", + "scripts": { + "start": "node index.js", + "build": "cd ../front && yarn build", + "install": "cd node_modules && rm -rf http-signature && rm -rf request/node_modules/http-signature && mv @peertube/http-signature . && cd ../../front && yarn" + }, + "private": true, + "dependencies": { + "@elastic/elasticsearch": "^8.17.1", + "@peertube/http-signature": "^1.7.0", + "@supercharge/promise-pool": "^3.2.0", + "@types/morgan": "^1.9.9", + "activitypub-express": "^4.4.2", + "express": "^4.21.2", + "express-async-handler": "^1.2.0", + "express-rate-limit": "^7.5.0", + "express-validator": "^7.2.1", + "mongodb": "^4.17.2", + "morgan": "^1.10.0", + "node-schedule": "^2.1.1", + "rotating-file-stream": "^3.2.6", + "stream-json": "^1.9.1", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" + } +} diff --git a/back/served.txt b/back/served.txt new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/back/served.txt @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..11293b0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,55 @@ +services: + fediblock-instance: + image: fediblock-instance + build: . + hostname: fediblock-instance + container_name: fediblock-instance + restart: always + user: node + ports: + - "4000:4000" + depends_on: + - fediblock-mongodb + - fediblock-elasticsearch + networks: + fediblocknet: + + fediblock-mongodb: + image: mongo:4.4.28 + hostname: fediblock-mongodb + container_name: fediblock-mongodb + restart: always + command: --wiredTigerCacheSizeGB 0.5 + volumes: + - ./mongodb:/data/db + networks: + fediblocknet: + + fediblock-elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.17.3-amd64 + hostname: fediblock-elasticsearch + container_name: fediblock-elasticsearch + restart: always + environment: + - node.name=fediblock-elasticsearch + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms1g -Xmx1g" + - xpack.security.enabled=false + - indices.id_field_data.enabled=true + - discovery.type=single-node + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65535 + hard: 65535 + volumes: + - ./elasticsearch-data:/usr/share/elasticsearch/data + expose: + - 9200 + networks: + fediblocknet: + +networks: + fediblocknet: diff --git a/front/.gitignore b/front/.gitignore new file mode 100644 index 0000000..f1f2610 --- /dev/null +++ b/front/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage + +# production +build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/front/README.md b/front/README.md new file mode 100644 index 0000000..58beeac --- /dev/null +++ b/front/README.md @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in your browser. + +The page will reload when you make changes.\ +You may also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can't go back!** + +If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. + +You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `npm run build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/front/package.json b/front/package.json new file mode 100644 index 0000000..ea4fbcc --- /dev/null +++ b/front/package.json @@ -0,0 +1,38 @@ +{ + "name": "front", + "version": "0.1.0", + "private": true, + "dependencies": { + "cra-template": "1.2.0", + "dayjs": "^1.11.13", + "html2canvas": "^1.4.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-scripts": "5.0.1", + "web-vitals": "^4.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/front/public/favicon.ico b/front/public/favicon.ico new file mode 100644 index 0000000..d14c02d Binary files /dev/null and b/front/public/favicon.ico differ diff --git a/front/public/index.html b/front/public/index.html new file mode 100644 index 0000000..05667d4 --- /dev/null +++ b/front/public/index.html @@ -0,0 +1,21 @@ + + + + + Fediblock Instance Φ + + + + + + + + + + + + + +
+ + diff --git a/front/public/logo192.png b/front/public/logo192.png new file mode 100644 index 0000000..fc44b0a Binary files /dev/null and b/front/public/logo192.png differ diff --git a/front/public/logo512.png b/front/public/logo512.png new file mode 100644 index 0000000..a4e47a6 Binary files /dev/null and b/front/public/logo512.png differ diff --git a/front/public/manifest.json b/front/public/manifest.json new file mode 100644 index 0000000..fb805b4 --- /dev/null +++ b/front/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "New Fediblock Instance", + "name": "New Fediblock Instance Front", + "icons": [ + { + "src": "favicon.ico", + "sizes": "78x90 72x72", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/front/public/robots.txt b/front/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/front/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/front/src/App.css b/front/src/App.css new file mode 100644 index 0000000..6ce1625 --- /dev/null +++ b/front/src/App.css @@ -0,0 +1,405 @@ +:root[data-theme="dark"] { + --background-color: #212529; + --color: #fefefe; + --instance-list: #333333; + --footer: #cccccc; +} + +:root[data-theme="light"] { + --background-color: #f5f5f5; + --color: #333333; + --instance-list: #f0f0f0; + --footer: #666666; +} + +body { + background-color: var(--background-color); + text-align: center; + font-family: Verdana, Geneva, Tahoma, sans-serif; + scrollbar-width: thin; +} + +h1, +h3, +h4, +.scan { + margin: 0 auto; + color: var(--color); + text-align: center; +} + +h3 { + padding: 10px 0; + border-bottom: 1px solid var(--footer); + margin-bottom: 1rem; +} + +a, +a:hover { + color: var(--color); + text-decoration: none; +} + +input[type="text"] { + padding: 10px; + border: none; + border-bottom: 1px solid var(--footer); + background-color: var(--instance-list); + color: var(--color); +} + +.placeholder { + font-size: small; + margin: 0 auto; + color: var(--color); +} + +.title, .placeholder a, footer a, .download-csv { + cursor: pointer; +} + +hr { + margin: 1rem auto; + border-bottom: 1px solid var(--footer); + width: 50%; +} + +.tooltip { + visibility: hidden; + font-size: small; + text-align: left; + width: 2.2in; + background-color: var(--background-color); + color: var(--color); + border-radius: 5px; + padding: 1rem 1rem 0 0; + position: absolute; + z-index: 1; + opacity: 0; + transition: 0.5s; + border: 1px solid var(--footer); +} + +.count:hover .tooltip { + visibility: visible; + opacity: 1; +} + +.reverse { + margin: 0.5rem; + padding: 0.5rem; + border: 1px solid var(--footer); + background-color: var(--color); + color: var(--background-color); +} + +.reverse:hover { + color: var(--color); + background-color: var(--background-color); + cursor: pointer; +} + +.instancelist { + list-style-type: none; + margin: 20px 0; + padding: 0; + color: var(--color); + max-height: 58vh; + overflow: auto; + scrollbar-width: thin; +} + +@media (min-width: 992px) { + .instancelist li { + width: 30%; + } + + .instancelist li:hover { + width: 35%; + } + + .instance, + .placeholder, + h4 { + width: 30%; + } + + .modal-content { + width: 28%; + } +} + +@media (min-width: 600px) and (max-width: 992px) { + .instancelist li { + width: 60%; + } + + .instancelist li:hover { + width: 65%; + } + + .instance, + .placeholder, + h4 { + width: 60%; + } + + .modal-content { + width: 58%; + } +} + +@media (max-width: 600px) { + .instancelist li { + width: 90%; + } + + .instancelist li:hover { + width: 95%; + } + + .instance, + .placeholder, + h4 { + width: 90%; + } + + .api { + display: none; + } + + .modal-content { + width: 88%; + } +} + +.instancelist li { + color: var(--color); + padding: 10px; + border-bottom: 1px solid var(--footer); + margin: 0 auto; + max-height: 1.5em; + overflow: hidden; + min-height: 1.5em; + line-height: 1.5; +} + +.instancelist li:hover { + background-color: var(--instance-list); + cursor: pointer; + max-height: fit-content; +} + +.instancelist li img { + width: 70%; + margin: 0 auto; +} + +@keyframes opacity { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +footer { + text-align: center; + font-size: 12px; + padding: 10px; + color: var(--footer); +} + +.count { + text-decoration: underline dotted var(--color); + cursor: help; + position: relative; + display: inline-block; + text-underline-offset: 2px; +} + +.blocklist { + width: 90%; + text-align: left; + list-style: none; + padding: 1rem; +} + +.blocklist li { + color: var(--color); + white-space: nowrap; + word-wrap: break-word; + overflow: hidden; +} + +.blocklist li:hover { + white-space: inherit; + cursor: pointer; + color: var(--color); +} + +.blocklist li a, +.blocklist li a:hover, +.blocklist li a:visited { + color: var(--color); + text-decoration: underline; +} + +.blockinstance a, +.blockinstance a:hover { + color: var(--color); + text-decoration: underline; + cursor: help; +} + +.blockcount { + font-weight: bolder; +} + +.modal { + display: none; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + scrollbar-width: thin; + background-color: rgba(0, 0, 0, 0.4); +} + +.modal-content { + position: relative; + background-color: var(--background-color); + margin: 10px auto; + padding: 0; + border: 1px solid var(--footer); + -webkit-animation-name: animatetop; + -webkit-animation-duration: 0.4s; + animation-name: animatetop; + animation-duration: 0.4s; +} + +.modal-header { + padding: 6px; + background-color: var(--color); + color: var(--background-color); +} + +.modal-body { + color: var(--color); + background-color: var(--background-color); + padding: 6px; +} + +.modal-footer { + padding: 6px; + background-color: var(--color); + color: var(--background-color); +} + +.closemodal, +.download, +.capture { + color: var(--footer); + float: right; + font-size: 3rem; + font-weight: bolder; + margin: 0px 15px; +} + +.closemodal:hover, +.closemodal:focus, +.download:hover, +.download:focus, +.capture:hover, +.capture:focus { + color: var(--color); + text-decoration: none; + cursor: pointer; +} + +@keyframes animatetop { + from { + top: -300px; + opacity: 0; + } + + to { + top: 0; + opacity: 1; + } +} + +@keyframes animatebottom { + from { + top: 0; + opacity: 1; + } + + to { + top: -300px; + opacity: 0 + } +} + +h4 { + white-space: nowrap; + overflow: hidden; + position: relative; + border-left: 1px solid var(--background-color); + border-right: 1px solid var(--background-color); + transition: 0.2s; +} + +h4 p { + margin: 0.3rem 0; +} + +h4:hover { + width: 90%; +} + +.bounce { + display: inline-block; + animation: marquee 90s linear infinite; +} + +.bounce:hover { + animation-play-state: paused; +} + +@keyframes marquee { + 0% { + transform: translateX(100vw); + } + + 100% { + transform: translateX(-100%); + } +} + +.loader-content { + z-index: 0; + background-color: rgba(0, 0, 0, 0.4); + width: 100%; + height: 100%; + display: none; + position: fixed; + top: 0; + left: 0; + overflow: auto; + scrollbar-width: thin; +} + +.loader { + background-color: var(--color); + width: fit-content; + margin: 50vh auto; + z-index: 1; + position: relative; + color: var(--background-color); +} \ No newline at end of file diff --git a/front/src/App.js b/front/src/App.js new file mode 100644 index 0000000..e2258ef --- /dev/null +++ b/front/src/App.js @@ -0,0 +1,28 @@ +import { useState } from 'react'; +import './App.css'; +import Title from './component/Title'; +import Bar from './component/Bar'; +import Count from './component/Count'; +import Form from './component/Form'; +import Scan from './component/Scan'; +import Footer from './component/Footer'; +import Loader from './component/Loader'; + +function App() { + const [searchTerm, setSearchTerm] = useState(''), + [matrix, setMatrix] = useState('off') + return ( + <> + + <Bar setSearch={d => setSearchTerm(d)} /> + <Count /> + <Form searchTerm={searchTerm} matrix={matrix} /> + <Scan /> + <hr /> + <Footer setCurrentMatrix={m => setMatrix(m)} /> + <Loader /> + </> + ); +} + +export default App; diff --git a/front/src/App.test.js b/front/src/App.test.js new file mode 100644 index 0000000..1f03afe --- /dev/null +++ b/front/src/App.test.js @@ -0,0 +1,8 @@ +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(<App />); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/front/src/component/Bar.js b/front/src/component/Bar.js new file mode 100644 index 0000000..dab139c --- /dev/null +++ b/front/src/component/Bar.js @@ -0,0 +1,29 @@ +import { useEffect, useState, useCallback } from 'react'; +import dayjs from '../../node_modules/dayjs/'; +import relativeTime from '../../node_modules/dayjs/plugin/relativeTime'; +dayjs.extend(relativeTime) + +const Bar = (prop) => { + const [bounce, setBounce] = useState([]), + fillBounce = useCallback(async () => { + try { + const response = await fetch('/api/outbox'), + result = await response.json() + if (result && result.length > 0) { + setBounce(result) + } + } catch (e) { + console.error(e) + } + }) + useEffect(() => { + fillBounce() + }, []) + return ( + <h4> + <p className="bounce">{bounce && bounce.length > 0 ? bounce.map(b => <a onClick={() => prop.setSearch(b.content.replace(/.*#/g, '').replace(/\<\/a\>\<\/p\>/, ''))}>{b.content.replace(/<[^>]*>/g, '').replace(new RegExp(new URL(window.location.href).host), '')} - {dayjs().to(b.published)}</a>).reduce((prev, curr) => [prev, ' | ', curr]) : []}</p> + </h4> + ) +} + +export default Bar; \ No newline at end of file diff --git a/front/src/component/Count.js b/front/src/component/Count.js new file mode 100644 index 0000000..facdcff --- /dev/null +++ b/front/src/component/Count.js @@ -0,0 +1,41 @@ +import { useEffect, useState, useCallback } from 'react'; + +const Count = () => { + const [count, setCount] = useState('0'), + [statsres, setStatsres] = useState(''), + fillStats = useCallback(async () => { + try { + const [res, stats] = await Promise.all([(await fetch('/api/count')).json(), (await fetch('/api/stats')).json()]) + setCount(res.count) + setStatsres(stats) + } catch (e) { + console.error(e) + } + }) + useEffect(() => { + (async () => { + await fillStats() + })() + }, []) + return ( + <h3>Search in <a href="/api/stats" target="_blank"> + <span className="count">{count}<div className="tooltip"> + <u><strong><center>STATS</center></strong></u> + <ul><li>Statuses AVG: {Math.round(statsres.status_avg)}</li> + <li>Statuses MAX: {statsres.status_max}</li> + <li>Domain AVG: {Math.round(statsres.domain_avg)}</li> + <li>Domain MAX: {statsres.domain_max}</li> + <li>Users AVG: {Math.round(statsres.user_avg)}</li> + <li>Users MAX: {statsres.user_max}</li> + <li>Stats Instances: {statsres.stats_filtered}</li> + <li>Total Instances: {statsres.instance_count}</li> + <li>Users by Instance: {(Math.round(statsres.user_avg) / statsres.instance_count).toFixed(2)}</li> + <li>Statuses by Domain: {(Math.round(statsres.status_avg) / Math.round(statsres.domain_avg)).toFixed(2)}</li> + <li>Statuses by User: {(Math.round(statsres.status_avg) / Math.round(statsres.user_avg)).toFixed(2)}</li> + </ul></div></span> + </a> public instances<span className="api"> - <a href="/api-docs" target="_blank">API docs</a></span> + </h3> + ) +} + +export default Count; \ No newline at end of file diff --git a/front/src/component/Footer.js b/front/src/component/Footer.js new file mode 100644 index 0000000..45b7e86 --- /dev/null +++ b/front/src/component/Footer.js @@ -0,0 +1,67 @@ +import { useEffect, useState, useCallback } from 'react'; +import Messenger from '../random-text'; + +const Footer = (prop) => { + const [theme, setTheme] = useState('dark'), + [matrix, setMatrix] = useState('off'), + [served, setServed] = useState({ served: 0, lastscan: 0, server: 0, instances: 0, peers: 0, created: 0, updated: 0 }), + loading = () => { + document.querySelector('.loader-content').style.display = 'initial' + setTimeout(() => { + document.querySelector('.loader-content').style.display = 'none' + }, 60 * 1000) + }, + toggleTheme = useCallback(() => { + let tm = document.documentElement.dataset.theme + if (!theme || tm === 'light') { + setTheme('dark') + } else { + setTheme('light') + } + }), + refreshServed = useCallback(async () => { + try { + const response = await fetch('/api/served') + if (response.ok) { + setServed(await response.json()) + } + } catch (e) { + console.error(e) + } + }), + toggleMatrix = useCallback(() => { + if (matrix === 'off') { + setMatrix('on') + prop.setCurrentMatrix('on') + } else { + setMatrix('off') + prop.setCurrentMatrix('off') + } + }) + useEffect(() => { + setTheme(theme) + document.documentElement.dataset.theme = theme + refreshServed() + if (matrix === 'on') { + var walker = document.createTreeWalker(document.getElementById('root'), NodeFilter.SHOW_TEXT) + while (walker.nextNode()) { + if (walker.currentNode.textContent.length > 1) { + new Messenger(walker.currentNode) + } + } + } + }, [theme]) + return ( + <footer>Served <span className="served">{served.served}</span> times - Last scan <span className="lastscan">{served.lastscan}</span> peers of <span + className="server">{served.server}</span><br /> + Total scanned <span id="instances">{served.instances}</span> instances with <span className="peers">{served.peers}</span> peers - <span + className="created">{served.created}</span> created - <span className="updated">{served.updated}</span> updated<br /> + matrix <a className="matrix" onClick={() => toggleMatrix()}>{matrix}</a> - download json <a className="download_index" href="/api/download_index" + onClick={() => loading()} download="fediblock-index.json.gz" target="_blank">index</a> - + by <a href="https://about.manalejandro.com" target="_blank">ale</a> <s>©</s>2025  + <a className="darklight" onClick={() => toggleTheme()}>{!theme || theme === 'dark' ? '☼' : '☽'}</a> + </footer> + ) +} + +export default Footer; \ No newline at end of file diff --git a/front/src/component/Form.js b/front/src/component/Form.js new file mode 100644 index 0000000..5ff83ff --- /dev/null +++ b/front/src/component/Form.js @@ -0,0 +1,167 @@ +import { useEffect, useState, useCallback, useRef } from 'react'; +import Modal from './Modal'; +import Messenger from '../random-text'; + +const Form = (prop) => { + let csv + const [searchTerm, setSearchTerm] = useState(''), + [list, setList] = useState([]), + [typeList, setTypeList] = useState('ranking'), + [domain, setDomain] = useState({ domain: '' }), + [reverse, setReverse] = useState(false), + refList = useRef(null), + refDownload = useRef(null), + loading = () => { + document.querySelector('.loader-content').style.display = 'initial' + }, + download = () => { + if (csv.split('\n').length > 2) { + refDownload.current.href = window.URL.createObjectURL(new Blob([csv], { type: 'text/csv' })) + refDownload.current.download = 'fediblock-top100.csv' + } + }, + listinstance = useCallback(async content => { + loading() + if (content && content.length > 0) { + try { + const result = await fetch('/api/list/' + content), + res = await result.json() + if (res && Array.isArray(res.instances) && Array.isArray(res.suggests)) { + setTypeList('list') + setList(res) + document.querySelector('.loader-content').style.display = 'none' + } else { + document.querySelector('.loader-content').style.display = 'none' + window.alert('Error: No response') + } + } catch (e) { + document.querySelector('.loader-content').style.display = 'none' + window.alert('Error: ' + e.message) + } + } else { + document.querySelector('.loader-content').style.display = 'none' + } + }), + ranking = useCallback(async () => { + loading() + try { + const result = await fetch('/api/ranking'), + res = await result.json() + if (Array.isArray(res) && res.length > 0) { + setTypeList('ranking') + setList(res) + document.querySelector('.loader-content').style.display = 'none' + } else { + document.querySelector('.loader-content').style.display = 'none' + window.alert('Error: No response') + } + } catch (e) { + document.querySelector('.loader-content').style.display = 'none' + window.alert('Error: ' + e.message) + } + }), + filterKeys = useCallback(event => { + if (event.key && !event.ctrlKey && !event.altKey) { + if ((event.key.length === 1 && /[a-z0-9.\-*:]/i.test(event.key)) || (event.key === 'Backspace' && event.target.value !== '')) { + if (event.key === '.' && event.target.value === '') { + return + } else if (event.key === 'Backspace' && event.target.value !== '') { + setSearchTerm(searchTerm.substring(0, searchTerm.length - 1)) + } else { + setSearchTerm(searchTerm + event.key) + } + } + } + }) + useEffect(() => { + if (searchTerm && searchTerm.length > 0) { + listinstance(searchTerm) + window.location.hash = searchTerm + } else { + (async () => { + await ranking() + })() + } + if (prop.matrix === 'on') { + var walker = document.createTreeWalker(refList.current, NodeFilter.SHOW_TEXT) + while (walker.nextNode()) { + if (walker.currentNode.textContent.length > 1) { + new Messenger(walker.currentNode) + } + } + } + }, [searchTerm, prop.matrix]) + useEffect(() => { + if (prop.searchTerm && prop.searchTerm.length > 0) { + setSearchTerm(prop.searchTerm) + } + }, [prop.searchTerm]) + useEffect(() => { + if (window.location.hash && window.location.hash !== '#') { + setSearchTerm(window.location.hash.substring(1)) + } + }, []) + return ( + <> + <section> + <input + autoFocus + type="text" + autoComplete="off" + className="instance" + placeholder="Type the name of the instance" + onKeyUp={(e) => filterKeys(e)} + value={searchTerm} /> + <button className="reverse" title="Reverse search..." onClick={() => { + if (searchTerm && searchTerm.length > 0) { + setReverse(true) + setDomain({ domain: searchTerm }) + loading() + } + }}>Reverse</button> + <div className="placeholder">{typeList === 'list' ? list.suggests && list.suggests.length > 0 ? list.suggests.map(suggest => (<a onClick={() => setSearchTerm(suggest)}>{suggest}</a>)).reduce((prev, curr) => [prev, ', ', curr]) : [] : []}</div> + <ul className="instancelist" ref={refList}>{typeList === 'list' ? list.instances.map(r => + (<li><a onClick={() => { + if (r.blocks) { + setReverse(false) + setDomain({ domain: r.domain }) + loading() + } else { + var a = document.createElement('a') + a.href = '/api/detail_api/' + r.domain + a.title = 'API info for ' + r.domain + a.target = '_blank' + a.dispatchEvent(new MouseEvent('click')) + } + }}>{r.domain}</a> + <span dangerouslySetInnerHTML={{ __html: r.blocks ? ` - ` + r.blocks + ` blocks ` : ` ` }}></span> + <span dangerouslySetInnerHTML={{ + __html: r.nodeinfo ? `<a href="/api/detail_nodeinfo/${r.domain}" title="Nodeinfo for ${r.domain}" target="_blank">ⓘ</a>` + `<br />` : `<br />` + }}></span> + <span dangerouslySetInnerHTML={{ + __html: r.api?.title ? `<br /><br />` + r.api.title + ` - ` + r.api.uri + `<br />` : `<br /><br />` + }}></span> + <span dangerouslySetInnerHTML={{ __html: r.last ? `Last update: ` + (new Date(r.last)).toLocaleString() + `<br />` : `` }}></span> + <span dangerouslySetInnerHTML={{ __html: r.api?.email ? `Email: ` + r.api.email + `<br />` : `` }}></span> + <span dangerouslySetInnerHTML={{ __html: `Registration: ` + (r.api?.registrations ? `open` : `closed`) + ` - Version: ` + r.api?.version + `<br />` }}></span> + <span dangerouslySetInnerHTML={{ __html: r.api?.stats ? `Users: ` + r.api.stats.user_count + ` - Statuses: ` + r.api.stats.status_count + ` - Domains: ` + r.api.stats.domain_count + `<br />` : `` }}></span> + <span dangerouslySetInnerHTML={{ __html: r.api?.description ? `Description: ` + r.api.description + `<br />` : `` }}></span> + <span dangerouslySetInnerHTML={{ __html: r.api?.thumbnail ? `<img domain="${r.domain}" loading="lazy" src="${r.api.thumbnail}" />` + `<br />` : `` }}></span> + </li> + )) : typeList === 'ranking' ? list.map((r, i) => { + if (i === 0) { + csv = '#domain,#severity,#reject_media,#reject_reports,#public_comment,#obfuscate\n' + return (<><li><strong>Top 100</strong><br /><small className="download-top"><a ref={refDownload} onClick={() => download()}>(Import CSV)</a></small></li><li>{i + 1} - {r.domain} - {r.count} blocks</li></>) + } else { + csv += !r.domain.match(/\*/) ? r.domain + ',suspend,False,False,"suspended by top 100 of ' + new URL(window.location.href).host + '",False\n' : '' + return (<li>{i + 1} - {r.domain} - {r.count} blocks</li>) + } + }) : ''} + </ul> + </section > + <Modal domain={domain} reverse={reverse} setSearch={d => setSearchTerm(d)} matrix={prop.matrix} /> + </> + ) +} + +export default Form; \ No newline at end of file diff --git a/front/src/component/Loader.js b/front/src/component/Loader.js new file mode 100644 index 0000000..bba45dc --- /dev/null +++ b/front/src/component/Loader.js @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react'; +import '../loaders.css'; + +const Loader = () => { + const loaders = ['loader-pong', 'loader-pacman', 'loader-abyss', 'loader-jump', 'loader-loading', 'loader-avenger', 'loader-mario'], + [load, setLoad] = useState('load ' + loaders[Math.floor(Math.random() * loaders.length)]) + useEffect(() => { + setLoad('load ' + loaders[Math.floor(Math.random() * loaders.length)]) + }, []) + return ( + <div className="loader-content"> + <div className="loader"> + <div className={load}></div> + </div> + </div> + ) +} + +export default Loader; \ No newline at end of file diff --git a/front/src/component/Modal.js b/front/src/component/Modal.js new file mode 100644 index 0000000..4d7b833 --- /dev/null +++ b/front/src/component/Modal.js @@ -0,0 +1,142 @@ +import { useEffect, useCallback, useState, useRef } from 'react'; +import html2canvas from '../../node_modules/html2canvas/dist/html2canvas.js'; +import Messenger from '../random-text.js'; + +const Modal = (prop) => { + let csv + const [blocktitle, setBlocktitle] = useState(''), + [blockcount, setBlockcount] = useState(''), + [blockinstance, setBlockinstance] = useState(''), + [blocktook, setBlocktook] = useState(''), + [blocklist, setBlocklist] = useState([]), + hrefCanvas = useRef(null), + refList = useRef(null), + loading = () => { + document.querySelector('.loader-content').style.display = 'initial' + }, + closeModal = event => { + if (event.target.classList.contains('modal') || event.target.classList.contains('closemodal')) { + document.querySelector('.modal-content').style.animationName = 'animatebottom' + } + }, + animationEnd = event => { + if (event.animationName === 'animatebottom') { + document.querySelector('.modal').style.display = 'none' + } + }, + capture = useCallback(async () => { + const canvas = await html2canvas(hrefCanvas.current, { useCORS: true }), + capture = document.querySelector('.capture') + capture.download = 'fediblock-' + Date.now() + '.png' + capture.href = canvas.toDataURL('image/png', 1.0) + capture.dispatchEvent(new MouseEvent('click')) + }), + download = () => { + const download = document.querySelector('.download') + if (csv.split('\n').length > 2) { + download.href = window.URL.createObjectURL(new Blob([csv], { type: 'text/csv' })) + download.download = 'fediblock-' + prop.domain.domain + '.csv' + } + }, + reverse = useCallback(async content => { + if (content && content.length > 0) { + loading() + const result = await fetch('/api/block_count/' + content), + res = await result.json() + if (res && Array.isArray(res.instances)) { + setBlocktitle('Reverse List') + setBlockcount(res.block_count) + setBlockinstance('ing ' + content) + setBlocktook('took ' + res.took + 'ms') + setBlocklist(res.instances) + document.querySelector('.loader-content').style.display = 'none' + document.querySelector('.modal-content').style.animationName = 'animatetop' + document.querySelector('.modal').style.display = 'block' + if (prop.matrix === 'on') { + var walker = document.createTreeWalker(refList.current, NodeFilter.SHOW_TEXT) + while (walker.nextNode()) { + if (walker.currentNode.textContent.length > 1) { + new Messenger(walker.currentNode) + } + } + } + } + } + }), + listblock = useCallback(async (domain) => { + const result = await fetch('/api/detail/' + domain), + res = await result.json() + if (res.blocks && Array.isArray(res.blocks)) { + csv = '#domain,#severity,#reject_media,#reject_reports,#public_comment,#obfuscate\n' + setBlocktitle('Blocked List') + setBlockcount(res.blocks.length) + setBlocktook('took ' + res.took + 'ms') + setBlockinstance(`ed by ` + (res.api ? `<a href="/api/detail_api/${res.instance}" title="API info for ${res.instance}" target="_blank">${res.instance}</a>` + : res.instance) + (res.nodeinfo ? ` <a href="/api/detail_nodeinfo/${res.instance}" title="Nodeinfo for ${res.instance}" target="_blank">ⓘ</a><br />` : `<br />`) + + `Last update: ` + (new Date(res.last)).toLocaleString()) + setBlocklist(res.blocks) + if (prop.matrix === 'on') { + var walker = document.createTreeWalker(refList.current, NodeFilter.SHOW_TEXT) + while (walker.nextNode()) { + if (walker.currentNode.textContent.length > 1) { + new Messenger(walker.currentNode) + } + } + } + } else { + var a = document.createElement('a') + a.href = '/api/detail_api/' + domain + a.title = 'API info for ' + domain + a.target = '_blank' + a.dispatchEvent(new MouseEvent('click')) + } + document.querySelector('.loader-content').style.display = 'none' + document.querySelector('.modal-content').style.animationName = 'animatetop' + document.querySelector('.modal').style.display = 'block' + }) + useEffect(() => { + if (prop.domain && prop.domain.domain.length > 0) { + if (prop.reverse) { + reverse(prop.domain.domain) + } else { + listblock(prop.domain.domain) + } + } + }, [prop.domain, prop.reverse]) + return ( + <div className="modal" onClick={(e) => closeModal(e)}> + <div className="modal-content" onAnimationEnd={(e) => animationEnd(e)} ref={refList}> + <div className="modal-header"> + <span className="closemodal" title="Close" onClick={(e) => closeModal(e)}>×</span> + <a className="capture" title="Take snapshot" onClick={capture} type="image/png" target="_blank">📷</a> + {!prop.reverse ? <a className="download" title="Download fediblock CSV mastodon file" onClick={download} type="text/csv" target="_blank">⇩</a> : ''} + <h2>{blocktitle}</h2> + </div> + <div className="modal-body" ref={hrefCanvas}> + <p> + <span className="blockcount">{blockcount}</span> public instances are block<span className="blockinstance" dangerouslySetInnerHTML={{ __html: blockinstance }}></span> + <br /><small className="blocktook">{blocktook}</small> + </p> + <ul className="blocklist">{prop.reverse ? blocklist.map((instance, index) => { + return (<li>{index + 1}. <a onClick={() => { + prop.setSearch(instance.instance) + document.querySelector('.modal-content').style.animationName = 'animatebottom' + }}>{instance.instance}</a>{instance.comment ? ' - ' + instance.comment : ''}</li>) + }) : blocklist.map((r, i) => { + if (r?.domain) { + csv += !r.domain.match(/\*/) ? r.domain + ',' + (r.severity ? r.severity : '') + ',False,False,' + (r.comment ? '"' + r.comment + '"' : '') + ',False\n' : '' + return (<li>{i + 1}. <a onClick={() => { + prop.setSearch(r.domain) + document.querySelector('.modal-content').style.animationName = 'animatebottom' + }}>{r.domain}</a>{r.severity ? ' - ' + r.severity : ''}{r.comment ? ' - ' + r.comment : ''}</li>) + } + })}</ul> + </div> + <div className="modal-footer"> + </div> + </div> + </div> + ) +} + +export default Modal; \ No newline at end of file diff --git a/front/src/component/Scan.js b/front/src/component/Scan.js new file mode 100644 index 0000000..acc3ba2 --- /dev/null +++ b/front/src/component/Scan.js @@ -0,0 +1,31 @@ +import { useEffect, useRef, useState } from 'react'; + +const Scan = () => { + const [scan, setScan] = useState('Scanning...'), + load = useRef(false) + useEffect(() => { + if (!load.current) { + const source = new window.EventSource('/api/scan') + source.onmessage = event => { + if (event.data) { + if (event.data.length > 0) { + setScan('Async Scanning' + event.data) + } else { + setScan('Async Scanning...') + } + } + } + source.onerror = error => { + console.error(error) + } + load.current = true + } + }) + return ( + <div> + <span className="scan">{scan}</span> + </div> + ) +} + +export default Scan; \ No newline at end of file diff --git a/front/src/component/Title.js b/front/src/component/Title.js new file mode 100644 index 0000000..17488f0 --- /dev/null +++ b/front/src/component/Title.js @@ -0,0 +1,13 @@ +import { useCallback } from 'react'; + +const Title = () => { + const click = useCallback(() => { + window.location.hash = '' + window.location.reload(false) + }) + return ( + <h1><a onClick={() => click()} className="title">Fediblock Instance Φ</a></h1> + ) +} + +export default Title; \ No newline at end of file diff --git a/front/src/index.js b/front/src/index.js new file mode 100644 index 0000000..b62b96a --- /dev/null +++ b/front/src/index.js @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + <React.StrictMode> + <App /> + </React.StrictMode> +); +reportWebVitals(); diff --git a/front/src/loaders.css b/front/src/loaders.css new file mode 100644 index 0000000..c1ec51c --- /dev/null +++ b/front/src/loaders.css @@ -0,0 +1,411 @@ +/* HTML: <div class="loader"></div> */ +.loader-pong { + width: 80px; + height: 70px; + border: 5px solid #000; + padding: 0 8px; + box-sizing: border-box; + background: + linear-gradient(#fff 0 0) 0 0/8px 20px, + linear-gradient(#fff 0 0) 100% 0/8px 20px, + radial-gradient(farthest-side, #fff 90%, #0000) 0 5px/8px 8px content-box, + #000; + background-repeat: no-repeat; + animation: l3 2s infinite linear; +} + +@keyframes l3 { + 25% { + background-position: 0 0, 100% 100%, 100% calc(100% - 5px) + } + + 50% { + background-position: 0 100%, 100% 100%, 0 calc(100% - 5px) + } + + 75% { + background-position: 0 100%, 100% 0, 100% 5px + } +} + +/* HTML: <div class="loader"></div> */ +.loader-pacman { + width: 90px; + height: 24px; + padding: 2px 0; + box-sizing: border-box; + display: flex; + animation: l5-0 3s infinite steps(6); + background: + linear-gradient(#000 0 0) 0 0/0% 100% no-repeat, + radial-gradient(circle 3px, #eeee89 90%, #0000) 0 0/20% 100% #000; + overflow: hidden; +} + +.loader-pacman::before { + content: ""; + width: 20px; + transform: translate(-100%); + border-radius: 50%; + background: #ffff2d; + animation: + l5-1 .25s .153s infinite steps(5) alternate, + l5-2 3s infinite linear; +} + +@keyframes l5-1 { + 0% { + clip-path: polygon(50% 50%, 100% 0, 100% 0, 0 0, 0 100%, 100% 100%, 100% 100%) + } + + 100% { + clip-path: polygon(50% 50%, 100% 65%, 100% 0, 0 0, 0 100%, 100% 100%, 100% 35%) + } +} + +@keyframes l5-2 { + 100% { + transform: translate(90px) + } +} + +@keyframes l5-0 { + 100% { + background-size: 120% 100%, 20% 100% + } +} + +/* HTML: <div class="loader"></div> */ +.loader-abyss { + width: 80px; + height: 60px; + box-sizing: border-box; + background: + linear-gradient(#fff 0 0) left /calc(50% - 15px) 8px no-repeat, + linear-gradient(#fff 0 0) right/calc(50% - 15px) 8px no-repeat, + conic-gradient(from 135deg at top, #0000, red 1deg 90deg, #0000 91deg) bottom/14px 8px repeat-x, + #000; + border-bottom: 2px solid red; + position: relative; + overflow: hidden; + animation: l6-0 1s infinite linear; +} + +.loader-abyss::before { + content: ""; + position: absolute; + width: 10px; + height: 14px; + background: lightblue; + left: -5px; + animation: + l6-1 2s infinite cubic-bezier(0, 100, 1, 100), + l6-2 2s infinite linear; +} + +@keyframes l6-0 { + 50% { + background-position: left, right, bottom -2px left -4px + } +} + +@keyframes l6-1 { + + 0%, + 27% { + bottom: calc(50% + 4px) + } + + 65%, + 100% { + bottom: calc(50% + 4.1px) + } +} + +@keyframes l6-2 { + 100% { + left: 100% + } +} + +/* HTML: <div class="loader"></div> */ +.loader-jump { + width: 70px; + height: 50px; + box-sizing: border-box; + background: + conic-gradient(from 135deg at top, #0000, #fff 1deg 90deg, #0000 91deg) right -20px bottom 8px/18px 9px, + linear-gradient(#fff 0 0) bottom/100% 8px, + #000; + background-repeat: no-repeat; + border-bottom: 8px solid #000; + position: relative; + animation: l7-0 2s infinite linear; +} + +.loader-jump::before { + content: ""; + position: absolute; + width: 10px; + height: 14px; + background: lightblue; + left: 10px; + animation: l7-1 2s infinite cubic-bezier(0, 200, 1, 200); +} + +@keyframes l7-0 { + 100% { + background-position: left -20px bottom 8px, bottom + } +} + +@keyframes l7-1 { + + 0%, + 50% { + bottom: 8px + } + + 90%, + 100% { + bottom: 8.1px + } +} + +/* HTML: <div class="loader"></div> */ +.loader-loading { + width: fit-content; + font-size: 17px; + font-family: monospace; + line-height: 1.4; + font-weight: bold; + --c: no-repeat linear-gradient(#000 0 0); + background: var(--c), var(--c), var(--c), var(--c), var(--c), var(--c), var(--c); + background-size: calc(1ch + 1px) 100%; + border-bottom: 10px solid #0000; + position: relative; + animation: l8-0 3s infinite linear; + clip-path: inset(-20px 0); +} + +.loader-loading::before { + content: "Loading"; +} + +.loader-loading::after { + content: ""; + position: absolute; + width: 10px; + height: 14px; + background: #25adda; + left: -10px; + bottom: 100%; + animation: l8-1 3s infinite linear; +} + +@keyframes l8-0 { + + 0%, + 12.5% { + background-position: calc(0*100%/6) 0, calc(1*100%/6) 0, calc(2*100%/6) 0, calc(3*100%/6) 0, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0 + } + + 25% { + background-position: calc(0*100%/6) 40px, calc(1*100%/6) 0, calc(2*100%/6) 0, calc(3*100%/6) 0, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0 + } + + 37.5% { + background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 0, calc(3*100%/6) 0, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0 + } + + 50% { + background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 0, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0 + } + + 62.5% { + background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 40px, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0 + } + + 75% { + background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 40px, calc(4*100%/6) 40px, calc(5*100%/6) 0, calc(6*100%/6) 0 + } + + 87.4% { + background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 40px, calc(4*100%/6) 40px, calc(5*100%/6) 40px, calc(6*100%/6) 0 + } + + 100% { + background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 40px, calc(4*100%/6) 40px, calc(5*100%/6) 40px, calc(6*100%/6) 40px + } +} + +@keyframes l8-1 { + 100% { + left: 115% + } +} + +/* HTML: <div class="loader"></div> */ +.loader-avenger { + width: fit-content; + font-size: 17px; + font-family: monospace; + line-height: 1.4; + font-weight: bold; + background: + linear-gradient(#000 0 0) left, + linear-gradient(#000 0 0) right; + background-repeat: no-repeat; + border-right: 5px solid #0000; + border-left: 5px solid #0000; + background-origin: border-box; + position: relative; + animation: l9-0 2s infinite; +} + +.loader-avenger::before { + content: "Loading"; +} + +.loader-avenger::after { + content: ""; + position: absolute; + top: 100%; + left: 0; + width: 22px; + height: 60px; + background: + linear-gradient(90deg, #000 4px, #0000 0 calc(100% - 4px), #000 0) bottom /22px 20px, + linear-gradient(90deg, red 4px, #0000 0 calc(100% - 4px), red 0) bottom 10px left 0/22px 6px, + linear-gradient(#000 0 0) bottom 3px left 0 /22px 8px, + linear-gradient(#000 0 0) bottom 0 left 50%/8px 16px; + background-repeat: no-repeat; + animation: l9-1 2s infinite; +} + +@keyframes l9-0 { + + 0%, + 25% { + background-size: 50% 100% + } + + 25.1%, + 75% { + background-size: 0 0, 50% 100% + } + + 75.1%, + 100% { + background-size: 0 0, 0 0 + } +} + +@keyframes l9-1 { + 25% { + background-position: bottom, bottom 54px left 0, bottom 3px left 0, bottom 0 left 50%; + left: 0 + } + + 25.1% { + background-position: bottom, bottom 10px left 0, bottom 3px left 0, bottom 0 left 50%; + left: 0 + } + + 50% { + background-position: bottom, bottom 10px left 0, bottom 3px left 0, bottom 0 left 50%; + left: calc(100% - 22px) + } + + 75% { + background-position: bottom, bottom 54px left 0, bottom 3px left 0, bottom 0 left 50%; + left: calc(100% - 22px) + } + + 75.1% { + background-position: bottom, bottom 10px left 0, bottom 3px left 0, bottom 0 left 50%; + left: calc(100% - 22px) + } +} + +/* HTML: <div class="loader"></div> */ +.loader-mario { + width: fit-content; + font-size: 17px; + font-family: monospace; + line-height: 1.4; + font-weight: bold; + padding: 30px 2px 50px; + background: linear-gradient(#000 0 0) 0 0/100% 100% content-box padding-box no-repeat; + position: relative; + overflow: hidden; + animation: l10-0 2s infinite cubic-bezier(1, 175, .5, 175); +} + +.loader-mario::before { + content: "Loading"; + display: inline-block; + animation: l10-2 2s infinite; +} + +.loader-mario::after { + content: ""; + position: absolute; + width: 34px; + height: 28px; + top: 110%; + left: calc(50% - 16px); + background: + linear-gradient(90deg, #0000 12px, #f92033 0 22px, #0000 0 26px, #fdc98d 0 32px, #0000) bottom 26px left 50%, + linear-gradient(90deg, #0000 10px, #f92033 0 28px, #fdc98d 0 32px, #0000 0) bottom 24px left 50%, + linear-gradient(90deg, #0000 10px, #643700 0 16px, #fdc98d 0 20px, #000 0 22px, #fdc98d 0 24px, #000 0 26px, #f92033 0 32px, #0000 0) bottom 22px left 50%, + linear-gradient(90deg, #0000 8px, #643700 0 10px, #fdc98d 0 12px, #643700 0 14px, #fdc98d 0 20px, #000 0 22px, #fdc98d 0 28px, #f92033 0 32px, #0000 0) bottom 20px left 50%, + linear-gradient(90deg, #0000 8px, #643700 0 10px, #fdc98d 0 12px, #643700 0 16px, #fdc98d 0 22px, #000 0 24px, #fdc98d 0 30px, #f92033 0 32px, #0000 0) bottom 18px left 50%, + linear-gradient(90deg, #0000 8px, #643700 0 12px, #fdc98d 0 20px, #000 0 28px, #f92033 0 30px, #0000 0) bottom 16px left 50%, + linear-gradient(90deg, #0000 12px, #fdc98d 0 26px, #f92033 0 30px, #0000 0) bottom 14px left 50%, + linear-gradient(90deg, #fdc98d 6px, #f92033 0 14px, #222a87 0 16px, #f92033 0 22px, #222a87 0 24px, #f92033 0 28px, #0000 0 32px, #643700 0) bottom 12px left 50%, + linear-gradient(90deg, #fdc98d 6px, #f92033 0 16px, #222a87 0 18px, #f92033 0 24px, #f92033 0 26px, #0000 0 30px, #643700 0) bottom 10px left 50%, + linear-gradient(90deg, #0000 10px, #f92033 0 16px, #222a87 0 24px, #feee49 0 26px, #222a87 0 30px, #643700 0) bottom 8px left 50%, + linear-gradient(90deg, #0000 12px, #222a87 0 18px, #feee49 0 20px, #222a87 0 30px, #643700 0) bottom 6px left 50%, + linear-gradient(90deg, #0000 8px, #643700 0 12px, #222a87 0 30px, #643700 0) bottom 4px left 50%, + linear-gradient(90deg, #0000 6px, #643700 0 14px, #222a87 0 26px, #0000 0) bottom 2px left 50%, + linear-gradient(90deg, #0000 6px, #643700 0 10px, #0000 0) bottom 0px left 50%; + background-size: 34px 2px; + background-repeat: no-repeat; + animation: inherit; + animation-name: l10-1; +} + +@keyframes l10-0 { + + 0%, + 30% { + background-position: 0 0px + } + + 50%, + 100% { + background-position: 0 -0.1px + } +} + +@keyframes l10-1 { + + 50%, + 100% { + top: 109.5% + } +} + +@keyframes l10-2 { + + 0%, + 30% { + transform: translateY(0); + } + + 80%, + 100% { + transform: translateY(-260%); + } +} \ No newline at end of file diff --git a/front/src/logo.svg b/front/src/logo.svg new file mode 100644 index 0000000..9dfc1c0 --- /dev/null +++ b/front/src/logo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg> \ No newline at end of file diff --git a/front/src/random-text.js b/front/src/random-text.js new file mode 100644 index 0000000..b96682e --- /dev/null +++ b/front/src/random-text.js @@ -0,0 +1,69 @@ +var Messenger = function (el) { + 'use strict'; + var m = this; + + m.init = function () { + m.codeletters = "&#*+%?£@§$"; + m.current_length = 0; + m.fadeBuffer = false; + m.message = el.textContent.length > 0 ? el.textContent : '' + + setTimeout(m.animateIn, 300); + }; + + m.generateRandomString = function (length) { + var random_text = ''; + while (random_text.length < length) { + random_text += m.codeletters.charAt(Math.floor(Math.random() * m.codeletters.length)); + } + + return random_text; + }; + + m.animateIn = function () { + if (m.current_length < m.message.length) { + m.current_length = m.current_length + 2; + if (m.current_length > m.message.length) { + m.current_length = m.message.length; + } + + var message = m.generateRandomString(m.current_length); + el.textContent = message; + + setTimeout(m.animateIn, 60); + } else { + setTimeout(m.animateFadeBuffer, 60); + } + }; + + m.animateFadeBuffer = function () { + if (m.fadeBuffer === false) { + m.fadeBuffer = []; + for (var i = 0; i < m.message.length; i++) { + m.fadeBuffer.push({ c: (Math.floor(Math.random() * 12)) + 1, l: m.message.charAt(i) }); + } + } + + var do_cycles = false; + var message = ''; + + for (var i = 0; i < m.fadeBuffer.length; i++) { + var fader = m.fadeBuffer[i]; + if (fader.c > 0) { + do_cycles = true; + fader.c--; + message += m.codeletters.charAt(Math.floor(Math.random() * m.codeletters.length)); + } else { + message += fader.l; + } + } + + el.textContent = message; + + setTimeout(m.animateFadeBuffer, 150); + }; + + m.init(); +} + +module.exports = Messenger; \ No newline at end of file diff --git a/front/src/reportWebVitals.js b/front/src/reportWebVitals.js new file mode 100644 index 0000000..5253d3a --- /dev/null +++ b/front/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/front/src/setupTests.js b/front/src/setupTests.js new file mode 100644 index 0000000..8f2609b --- /dev/null +++ b/front/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom';