From ca608c3c41e385c2ff89a5d09f10924744acdb8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=8C=E6=A3=AE?= Date: Wed, 7 Aug 2024 13:42:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83=E5=BA=94=E7=94=A8=20Iframely?= =?UTF-8?q?=20=E5=BA=94=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 萌森 --- apps/iframely/2.4.3/config/config.local.js | 339 +++++++++++++++++++++ apps/iframely/2.4.3/data.yml | 24 ++ apps/iframely/2.4.3/docker-compose.yml | 25 ++ apps/iframely/2.4.3/scripts/init.sh | 15 + apps/iframely/2.4.3/scripts/uninstall.sh | 10 + apps/iframely/2.4.3/scripts/upgrade.sh | 15 + apps/iframely/README.md | 46 +++ apps/iframely/data.yml | 18 ++ apps/iframely/logo.png | Bin 0 -> 14374 bytes 9 files changed, 492 insertions(+) create mode 100644 apps/iframely/2.4.3/config/config.local.js create mode 100644 apps/iframely/2.4.3/data.yml create mode 100644 apps/iframely/2.4.3/docker-compose.yml create mode 100644 apps/iframely/2.4.3/scripts/init.sh create mode 100644 apps/iframely/2.4.3/scripts/uninstall.sh create mode 100644 apps/iframely/2.4.3/scripts/upgrade.sh create mode 100644 apps/iframely/README.md create mode 100644 apps/iframely/data.yml create mode 100644 apps/iframely/logo.png diff --git a/apps/iframely/2.4.3/config/config.local.js b/apps/iframely/2.4.3/config/config.local.js new file mode 100644 index 000000000..d3cc8f436 --- /dev/null +++ b/apps/iframely/2.4.3/config/config.local.js @@ -0,0 +1,339 @@ +import {fileURLToPath} from 'url'; +import {dirname} from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Optional SSL cert, if you serve under HTTPS. +/* +const ssl_key = const file = await readFile(new URL('./key.pem', import.meta.url), {encoding: 'utf8'}); +const ssl_cert = await readFile(new URL('./cert.pem', import.meta.url), {encoding: 'utf8'}); +*/ + +export default { + + // Specify a path for custom plugins. Custom plugins will override core plugins. + // CUSTOM_PLUGINS_PATH: __dirname + '/yourcustom-plugin-folder', + + DEBUG: false, + RICH_LOG_ENABLED: false, + + // For embeds that require render, baseAppUrl will be used as the host. + baseAppUrl: "http://localhost:8061", // use "https://yourdomain.com/path" where you have Iframely in your reverse proxy + relativeStaticUrl: "/r", + + // Or just skip built-in renders altogether + SKIP_IFRAMELY_RENDERS: true, + + // For legacy reasons the response format of Iframely open-source is + // different by default as it does not group the links array by rel. + // In order to get the same grouped response as in Cloud API, + // add `&group=true` to your request to change response per request + // or set `GROUP_LINKS` in your config to `true` for a global change. + GROUP_LINKS: true, + + // Number of maximum redirects to follow before aborting the page + // request with `redirect loop` error. + MAX_REDIRECTS: 4, + + SKIP_OEMBED_RE_LIST: [ + // /^https?:\/\/yourdomain\.com\//, + ], + + /* + // Used to pass parameters to the generate functions when creating HTML elements + // disableSizeWrapper: Don't wrap element (iframe, video, etc) in a positioned div + GENERATE_LINK_PARAMS: { + disableSizeWrapper: true + }, + */ + + port: 8061, //can be overridden by PORT env var + host: '0.0.0.0', // Dockers beware. See https://github.com/itteco/iframely/issues/132#issuecomment-242991246 + //can be overridden by HOST env var + + // Optional SSL cert, if you serve under HTTPS. + /* + ssl: { + key: ssl_key, + cert: ssl_cert, + port: 443 + }, + */ + + /* + Supported cache engines: + - no-cache - no caching will be used. + - node-cache - good for debug, node memory will be used (https://github.com/tcs-de/nodecache). + - redis - https://github.com/mranney/node_redis. + - memcached - https://github.com/3rd-Eden/node-memcached + */ + CACHE_ENGINE: 'node-cache', + CACHE_TTL: 0, // In seconds. + // 0 = 'never expire' for memcached & node-cache to let cache engine decide itself when to evict the record + // 0 = 'no cache' for redis. Use high enough (e.g. 365*24*60*60*1000) ttl for similar 'never expire' approach instead + + /* + // Redis mode (cluster or standard) + REDIS_MODE: 'standard', + */ + + /* + // Redis cache options. + REDIS_OPTIONS: { + socket: { + host: '127.0.0.1', + port: 6379 + } + }, + */ + + /* + // Redis cluster options. + REDIS_CLUSTER_OPTIONS: { + servers: [ + { + host: '10.0.0.10', + port: 6379 + }, + // ... + ], + }, + */ + + /* + // Memcached options. See https://github.com/3rd-Eden/node-memcached#server-locations + MEMCACHED_OPTIONS: { + locations: "127.0.0.1:11211" + } + */ + + /* + // Access-Control-Allow-Origin list. + allowedOrigins: [ + "*", + "http://another_domain.com" + ], + */ + + /* + // Uncomment to enable plugin testing framework. + tests: { + mongodb: 'mongodb://localhost:27017/iframely-tests', + single_test_timeout: 10 * 1000, + plugin_test_period: 2 * 60 * 60 * 1000, + relaunch_script_period: 5 * 60 * 1000 + }, + */ + + // If there's no response from remote server, the timeout will occur after + RESPONSE_TIMEOUT: 5 * 1000, //ms + + // Customize API calls to oembed endpoints. + // Must have: please add your `access_token` for Facebook and Instagram API calls + ADD_OEMBED_PARAMS: [{ + + re: [ // Endpoint's URL regexp array. + /^https:\/\/graph\.facebook\.com\/v[0-9\.]+\/instagram_oembed/i + ], + params: { // Custom query-string params object. + + // TODO: get your access Insagtam token as described + // on https://developers.facebook.com/docs/instagram/oembed/ + access_token: '', // The simplest way is + // to use `{app-id}|{app secret}` as access token + + // Add any other optional params + hidecaption: true + } + }, { + re: [/^https:\/\/graph\.facebook\.com\/v[0-9\.]+\/oembed_page/i], + params: { + // TODO: get your access token as described + // on https://developers.facebook.com/docs/plugins/oembed + access_token: '', // The simplest way is + // to use `{app-id}|{app secret}` as access token + + // Add any other optional params + show_posts: 0, + show_facepile: 0, + maxwidth: 600 + } + }, { + // match i=user or i=moment or i=timeline to configure these types invidually + // see params spec at https://dev.twitter.com/web/embedded-timelines/oembed + re: [/^https?:\/\/publish\.twitter\.com\/oembed\?i=user/i], + params: { + limit: 1, + maxwidth: 600 + } + }, { + // Facebook https://developers.facebook.com/docs/plugins/oembed/ + re: [/^https:\/\/graph\.facebook\.com\/v[0-9\.]+\/oembed_/i], + params: { + // TODO: get your access token as described + // on https://developers.facebook.com/docs/plugins/oembed + access_token: '', // The simplest way is + // to use `{app-id}|{app secret}` as access token + + // Add any other optional params, like skip script tag and fb-root div + // omitscript: true + } + }], + + /* Configure use of HTTP proxies, headers as needed. + You don't have to specify all options per regex - just what you need to override + */ + /* + PROXY: [{ + re: [/^https?:\/\/www\.domain\.com/], + + // Either `proxy`, or `proxy_url`, or none. + proxy: true, // Will fetch URL via echo service configured as PROXY_URL. See below. + // proxy_url: 'http://1.2.3.4:8080?url={url}', // Will fetch URL via this exact echo service, see below. + + user_agent: 'CHANGE YOUR AGENT', + headers: { + // HTTP headers + // Overrides previous params if overlapped. + }, + cache_ttl: 3600 // in seconds, cache response for 1 hour. + }], + + // Proxy now requires an echo service endpoint. + // See #354 and example code at + // https://gist.github.com/nleush/7916ee89f7b8d6f0cd478d7335702139 + PROXY_URL: 'http://1.2.3.4:8080?url={url}', // Iframely will add `?url=...` to this endpoint + */ + + // Customize API calls to 3rd parties. At the very least - configure required keys. + // For available provider options - please see the code of its domain plugin. + providerOptions: { + locale: "en_US", // ISO 639-1 two-letter language code, e.g. en_CA or fr_CH. + // Will be added as highest priotity in accept-language header with each request. + // Plus is used in FB, YouTube and perhaps other plugins + "twitter": { + "max-width": 550, + "min-width": 250, + hide_media: false, + hide_thread: false, + omit_script: false, + center: false, + // dnt: true, + cache_ttl: 100 * 365 * 24 * 3600 // 100 Years. + }, + readability: { + enabled: false + // allowPTagDescription: true // to enable description fallback to first paragraph + }, + images: { + loadSize: false, // if true, will try an load first bytes of all images to get/confirm the sizes + checkFavicon: false // if true, will verify all favicons + }, + tumblr: { + consumer_key: "INSERT YOUR VALUE" + // media_only: true // disables status embeds for images and videos - will return plain media + }, + google: { + // https://developers.google.com/maps/documentation/embed/guide#api_key + maps_key: "INSERT YOUR VALUE" + }, + + /* + // Optional Camo Proxy to wrap all images: https://github.com/atmos/camo + camoProxy: { + camo_proxy_key: "INSERT YOUR VALUE", + camo_proxy_host: "INSERT YOUR VALUE" + // ssl_only: true // will only proxy non-ssl images + }, + */ + + // List of query parameters to add to YouTube and Vimeo frames + // Start it with leading "?". Or omit alltogether for default values + // API key is optional, youtube will work without it too. + // It is probably the same API key you use for Google Maps. + youtube: { + // api_key: "INSERT YOUR VALUE", + // parts: [ "snippet", "player" ], // list of fields you want to use in the request, in most cases you only need those two + get_params: "?rel=0&showinfo=1", // https://developers.google.com/youtube/player_parameters, + fix_shorts_in_eu: true // Avoid consent redirect for EU servers + }, + vimeo: { + get_params: "?byline=0&badge=0" // https://developer.vimeo.com/player/embedding + }, + soundcloud: { + old_player: true // enables classic player + }, + giphy: { + media_only: true // disables branded player for gifs and returns just the image + }, + bandcamp: { + get_params: '/size=large/bgcol=333333/linkcol=ffffff/artwork=small/transparent=true/', + media: { + album: { + height: 472, + 'max-width': 700 + }, + track: { + height: 120, + 'max-width': 700 + } + } + }, + // Docs: https://dev.twitch.tv/docs/embed/video-and-clips + /* + twitch: { + parent: 'jsbin.com, null.jsbin.com, localhost' + }, + */ + }, + + // WHITELIST_WILDCARD, if present, will be added to whitelist as record for top level domain: "*" + // with it, you can define what parsers do when they run accross unknown publisher. + // If absent or empty, all generic media parsers will be disabled except for known domains + // More about format: https://iframely.com/docs/qa-format + + /* + WHITELIST_WILDCARD: { + "twitter": { + "player": "allow", + "photo": "deny" + }, + "oembed": { + "video": "allow", + "photo": "allow", + "rich": "deny", + "link": "deny" + }, + "og": { + "video": ["allow", "ssl", "responsive"] + }, + "iframely": { + "survey": "allow", + "reader": "allow", + "player": "allow", + "image": "allow" + }, + "html-meta": { + "video": ["allow", "responsive"], + "promo": "allow" + } + } + */ + + // The list of regexs to be ignored. Iframely will return 417 + // At minimum, keep your localhosts ignored to avoid SSRF + IGNORE_DOMAINS_RE: [ + /^https?:\/\/127\.0\.0\.1/i, + /^https?:\/\/localhost/i, + /^https?:\/\/[^\/]+:\d+\/?/, // Blocks port-scan via DNS pointing to 127.0.0.1 + + // And this is AWS metadata service + // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html + /^https?:\/\/169\.254\.169\.254/ + ], + + // Endpoint for prerender service, if you need it. Used to parse React apps. Very slow. + // Tested with https://github.com/prerender/prerender + // PRERENDER_URL: "https://domain/render?url=" +}; diff --git a/apps/iframely/2.4.3/data.yml b/apps/iframely/2.4.3/data.yml new file mode 100644 index 000000000..4fbe71b7e --- /dev/null +++ b/apps/iframely/2.4.3/data.yml @@ -0,0 +1,24 @@ +additionalProperties: + formFields: + - default: "/home/iframely" + edit: true + envKey: IFRAMELY_ROOT_PATH + labelZh: 数据持久化路径 + labelEn: Data persistence path + required: true + type: text + - default: 8061 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelZh: WebUI 端口 + labelEn: WebUI port + required: true + rule: paramPort + type: number + - default: "" + edit: true + envKey: HTTP_PROXY + labelZh: 网络代理 + labelEn: Network Proxy + required: false + type: text diff --git a/apps/iframely/2.4.3/docker-compose.yml b/apps/iframely/2.4.3/docker-compose.yml new file mode 100644 index 000000000..2c21b7005 --- /dev/null +++ b/apps/iframely/2.4.3/docker-compose.yml @@ -0,0 +1,25 @@ +version: "3.8" + +networks: + 1panel-network: + external: true + +services: + iframely: + image: qyg2297248353/iframely:v2.4.3 + container_name: ${CONTAINER_NAME} + labels: + createdBy: "Apps" + restart: always + networks: + - 1panel-network + ports: + - ${PANEL_APP_PORT_HTTP}:8080 + env_file: + - /etc/1panel/envs/global.env + - ${ENV_FILE:-/etc/1panel/envs/default.env} + volumes: + - ./config/config.local.js:/iframely/config.local.js + environment: + - NODE_ENV=production + - HTTPS_PROXY=${HTTP_PROXY} diff --git a/apps/iframely/2.4.3/scripts/init.sh b/apps/iframely/2.4.3/scripts/init.sh new file mode 100644 index 000000000..77b849120 --- /dev/null +++ b/apps/iframely/2.4.3/scripts/init.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +if [ -f .env ]; then + source .env + + # setup-1 add default values + CURRENT_DIR=$(pwd) + sed -i '/^ENV_FILE=/d' .env + echo "ENV_FILE=${CURRENT_DIR}/.env" >> .env + + echo "Check Finish." + +else + echo "Error: .env file not found." +fi diff --git a/apps/iframely/2.4.3/scripts/uninstall.sh b/apps/iframely/2.4.3/scripts/uninstall.sh new file mode 100644 index 000000000..c86c4fbca --- /dev/null +++ b/apps/iframely/2.4.3/scripts/uninstall.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +if [ -f .env ]; then + source .env + + echo "Check Finish." + +else + echo "Error: .env file not found." +fi diff --git a/apps/iframely/2.4.3/scripts/upgrade.sh b/apps/iframely/2.4.3/scripts/upgrade.sh new file mode 100644 index 000000000..77b849120 --- /dev/null +++ b/apps/iframely/2.4.3/scripts/upgrade.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +if [ -f .env ]; then + source .env + + # setup-1 add default values + CURRENT_DIR=$(pwd) + sed -i '/^ENV_FILE=/d' .env + echo "ENV_FILE=${CURRENT_DIR}/.env" >> .env + + echo "Check Finish." + +else + echo "Error: .env file not found." +fi diff --git a/apps/iframely/README.md b/apps/iframely/README.md new file mode 100644 index 000000000..09f0f7c5c --- /dev/null +++ b/apps/iframely/README.md @@ -0,0 +1,46 @@ +# Iframely + +当今互联网的富媒体平台 + + + +iframely 是针对所有各种富媒体嵌入和 URL 数据的统一交付服务。 + +![](https://img.shields.io/badge/%E6%96%B0%E7%96%86%E8%90%8C%E6%A3%AE%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E5%B7%A5%E4%BD%9C%E5%AE%A4-%E6%8F%90%E4%BE%9B%E6%8A%80%E6%9C%AF%E6%94%AF%E6%8C%81-blue) + +## 特点 + ++ 最佳富媒体 + +iframely 了解来自 1900 多家出版商的富媒体,并且还在不断增加。视频、音频、应用程序、照片和图像、幻灯片、播放列表、播客、地图、3D、表格、文档、谜题、测验、图表和信息图表。您会自动获得所有新的发布商。 + +## API 端点 + ++ 请求示例 + +```http request +${domain}/api/oembed?url={URL}&api_key={API_KEY} +``` + ++ 响应示例 + +```json +{ + "url": "https://vimeo.com/141567420", + "type": "video", + "version": "1.0", + "title": "Input/Output", + "description": "A new short from Terri Timely and Park Pictures", + "author": "Terri Timely", + "author_url": "https://vimeo.com/user1946955", + "provider_name": "Vimeo", + "thumbnail_url": "https://i.vimeocdn.com/…5aebf015a6472-d_295x166", + "thumbnail_width": 295, + "thumbnail_height": 166, + "html": "
" +} +``` + +--- + +![Ms Studio](https://file.lifebus.top/imgs/ms_blank_001.png) diff --git a/apps/iframely/data.yml b/apps/iframely/data.yml new file mode 100644 index 000000000..2bc8aec69 --- /dev/null +++ b/apps/iframely/data.yml @@ -0,0 +1,18 @@ +name: Iframely +title: 响应式 Web 嵌入和 URL 元的 Iframely API +description: 响应式 Web 嵌入和 URL 元的 Iframely API +additionalProperties: + key: iframely + name: Iframely + tags: + - Tool + - Middleware + - Local + shortDescZh: 响应式 Web 嵌入和 URL 元的 Iframely API + shortDescEn: Responsive Web Embeds and URL Meta with Iframely API + type: tool + crossVersionUpdate: true + limit: 0 + website: https://iframely.com/ + github: https://github.com/itteco/iframely/ + document: https://iframely.com/docs/ diff --git a/apps/iframely/logo.png b/apps/iframely/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b392f442a67eb46ffd3cb9bc8f06def4268fbed4 GIT binary patch literal 14374 zcmV+>IN8UEP)QZB42_)Nlu~sDAXv#Mp`RqQ3_Rp7)<5YAcO`H z3?dX|x29Dj8j&cdX`zQuLDCIK6ohnxC2XTi4V7^xVBADGi|28A=s`4rB=>XAykCE~ z+?K+%?9R+J&wTFB9VKOCoIWi8006)$mzWL!000172#PEK0068+kp%z%fR!k+00026 z5=9mO0035^$N~TWz)BQZ00013i6RRC001jdWB~vGU?qwy00022M3Dsm0DzS!vH$=8 zuo6WU00011qR0XO0KiHVSpWb4ScxJF0000hQDgxC0AMAGEC2uitVEFo004lMD6#+m z0I(88761SMR-(uP006*B6j=ZO09c743jhEBD^X+t003YmiYx#C0IWoj1pokml_;_R z006KOMHT=609K;N0ssKON)%ZD003BtA`1Wj04q^s0RRACC5kKn0068+kp%z%fR!k+ z000265=9mO0035^$N~TWz)BQZ00013i6RRC001jdWB~vGU?qwy00022M3Dsm0DzS! zvH$=8uo6WU00011qR0XO0KiHVSpWb4ScxJF0000hQDgxC0AMAGEC2uitVEFo004lM zD6#+m0I(88761SMR-(uP006*B6j=ZO09c743jhEBD^X+t003YmiYx#C0IWoj1pokm zl_;_R006KOMHT=609K;N0ssKON)%ZD003BtA`1Wj04q^s0RRACC5kKn0068+kp%z% zfR!k+000265=9mO0035^$N~TWz)BQZ00013i6RRC001jdWB~vGU?qwy00022M3Dsm z0DzS!vH$=8uo6WU00011qR0XO0KiHVSpWb4ScxJF0000hQDgxC0AMAGEC2uitVEFo z004lMD6#+m0I(88761SMR-(uP006*B6j=ZO09c743jhEBD^X+t003YmiYx#C0IWoj z1pokml_;_R006KOMHT=609K;N0ssKON)%ZD003BtA`1Wj04q^s0RRACC5kKn0068+ zkp%z%fR!k+000265=9mO0035^$N~TWz)BQZ00013i6RRC001jdWB~vGU?qwy00022 zM3Dsm0DzS!vH$=8uo6WU00011qR0ZUUgyJc8KyJXB_ZKkkDJZ_02hxU3&3JI{nFb> zkM8>EZ%ik#%M(}L^}aM~z%A5S09K>O0eB`D`+}TcpDPc^FC=;o4jF8wdDvM0FR@{0`RoL>#~kZeQK}1%XE^+(YZ42 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 z51KSJwblOprt<)ZG!$6?2GrMo z{#_}%?=w|W|Nb*)O539$BvxJxV^}_X1&S%d(Cwo(d!oZtDP3H*@_4Qv2k_rk+OQnJJv%f3-wd04+nN9@&4Z(~o0L!@h z4Q}h0e}xpkrQN%x^Vn`d)VhC2^ClFnG@S$h8jB~g04#Rad;vN0000hQDgxC0AMAGEC2uitVEFo004lMD6#+m z0I(88761SMR-(uP006*B6j=ZO09c743jhEBD^X+t003YmiYx#C0IWoj1pokml_;_R z006KOMHT=609K;N0ssKON)%ZD003BtA`1Wj04q^s0RRACC5kKn0068+kp%z%fR!k+ z000265=9mO0035^$N~TWz)BQZ00013i6RRC001jdWB~vGU?qwy00022M3Dsm0DzS! zvH$=8uo6WU00011qR0XO0KiHVSpWb4ScxJF0000hQDgxC0AMAGEC2uitVEFo004lM zD6#+m0I(88761SMR-(uP006*B6j=ZO09c743jhEBD^X+t003YmiYx#C0IWoj1pokm zl_;_R006KOMHT=609K;N0ssKON)%ZD003BtA`1Wj04q^s0RRACC5kKn0068+kp%z% zfR!k+000265=9mO0035^$N~TWz)BQZ00013i6RRC001jdWB~vGU?qwy00022M3Dsm z0DzS!vH$=8uo6WU00011qR0XO0KiHVSpWb4ScxJF0000hQDgxC0AMAGEC2uitVEFo z004lMD6#+m0I(88761SMR-(uP006*B6j=ZO09c743jhEBD^X+t003YmiYx#C0IWoj z1pokml_;_R006KOMHT=609K;N0ssKON)%ZD003BtA`1Wj04q^s0RRACC5kKn0068+ zkp%z%fR!k+000265=9mO0035^$N~TWz)BQZ00013i6RRC001jdWB~vGU?qwy00022 zM3Dsm0DzS!vH$=8uo6WU00011qR0XO0KiHVSpWb4ScxJF0000hQDgxC0AMAGEC2ui ztVEFo004lMD6#+m0I(88761SMR-(uP006*B6j=ZO09c743jhEBD^X+t003YmiYx#C g0Ic-?1^@v6|FoV9Sh5=x4*&oF07*qoM6N<$f)mU2#sB~S literal 0 KcmV+b0RR6000031