文档

通过外部地址让卡片保持最新

数据源是 screenRIG 按计划读取的地址。它把读取到的内容写入一张卡片的数据,这样无需任何人重新发布,卡片就能保持最新,显示该卡片的每块屏幕都会在内容变化时重新获取文档。Google、Outlook、iCloud 或 Nextcloud 日历会变成日程或会议室看板,表格或 API 会变成菜单或 KPI 看板,预设则能把 Slack 频道、Google 评价或招聘看板变成一张卡片。

数据源如何工作

  • 一个数据源对应一张卡片。 数据源会指定它所填充的卡片。一张卡片最多有一个数据源,一个组织跨其所有项目最多有 200 个。
  • 按计划运行。 日历数据源每 5 分钟同步一次,所有数据源都可设置为 1 分钟到 24 小时之间。数据源在创建或更改后几秒内运行,source refresh 可立即同步。
  • 同步失败绝不会改动卡片。 卡片保留最后一份有效文档,数据源的状态会说明出了什么问题。失败会自动重试,每次失败后等待更久。
  • 只写入变化。 同步发现内容与卡片已有内容相同时,不会写入任何内容,也不会唤醒屏幕。
  • 同步免费。 文档与其他写入一样,计入卡片数据存储。
  • 凭据经过加密。 能授予访问权限的地址会加密存储,只回显其协议和主机,绝不会出现在日志、错误或项目事件日志中。
  • 在您选定的期间内保留最后一次正常的数据。 数据源无法刷新时,其卡片会在该数据源的过期时长(--stale-after,1 分钟到 7 天;场馆预订数据源为 30 分钟)内保留最后一次正常的数据,之后数据在所有屏幕(包括离线屏幕)上过期,依赖它的页面会切换到其后的页面。未设置时长时,最后一次正常的数据会保留到数据源再次刷新。参见数据源无法刷新时。

从预设开始

预设是为人们常放到屏幕上的服务准备好的现成数据源。选一个,填几个字段,再加上该服务签发的凭据,screenRIG 就会创建数据源并把返回结果映射到卡片。预设是 json 数据源,所有套餐都包含。

预设卡片需要什么
slack-channelworkplace-posts, message-wall频道 ID,以及您工作区内部 Slack 应用的机器人令牌(xoxb-…):在 api.slack.com/apps 添加范围 channels:history 和 groups:history,安装应用,然后把它添加到频道
bonuslykudos带有 recognition:read 的 Bonusly 访问令牌,由 Global 或 Tech 管理员在 Company、Integrations、API & Tokens 中创建
facebook-pagesocial-wall主页 ID、要显示的名称,以及在 Graph API 测试工具中获取的、带有 pages_read_engagement 和 pages_show_list 的主页访问令牌
instagram-businesssocial-wallInstagram 账号 ID、要显示的名称,以及关联 Facebook 主页的访问令牌,需带有 instagram_basic、pages_show_list 和 pages_read_engagement
google-reviewsreviews地点 ID,以及已启用 Places API (New) 的 Google Cloud API 密钥
google-travel-timestravel-times出发地址、一到八个目的地、驾车或公共交通,以及已启用 Routes API 的 Google Cloud API 密钥
greenhouse-jobsnow-hiring来自 boards.greenhouse.io/TOKEN 的看板令牌。无需凭据:看板是公开的
lever-jobsnow-hiring来自 jobs.lever.co/NAME 的站点名称和区域。无需凭据
ashby-jobsnow-hiring来自 jobs.ashbyhq.com/NAME 的看板名称。无需凭据
github-actionsbuild-board所有者、仓库和分支,以及对该仓库 Actions 具有只读访问权限的细粒度个人访问令牌,在 github.com/settings/personal-access-tokens 创建

控制台、您的智能体和 API 都会列出这些预设,并附上每个字段的帮助说明和示例,以及获取各项凭据的步骤:GET /api/source-presets 返回全部预设,GET /api/source-presets/NAME 返回其中一个(见 API 参考的数据源分组)。创建数据源时,用 preset、settings 和 secrets 代替 kind 和 config。请把凭据放在请求文件中,不要放在命令行上,并把 BOT_TOKEN 替换为令牌:

{
  "name": "Announcements",
  "preset": "slack-channel",
  "target": {"card_id": "crd_CARD"},
  "settings": {"channel": "C0123456789", "channel_label": "#announcements"},
  "secrets": {"token": "BOT_TOKEN"}
}
curl -sS https://api.screenrig.ai/api/sources/test \
  -H "Authorization: Bearer $SCREENRIG_TOKEN" -H "Content-Type: application/json" -d @source.json

POST /api/sources/test 会输出卡片将显示的文档、卡片是否接受它以及发现的情况,并且不写入任何内容。POST /api/sources 保存同一个文件,带新 settings 的 PATCH /api/sources/ID 会基于预设重新构建数据源。

  • 您的账号,您的条款。 预设使用您创建的凭据读取您自己的账号,并遵守该服务的条款,卡片会显示这些条款要求的内容:Google 评价会带上 Google 标识和每位评价者的姓名。
  • 计划与过期。 每个预设都有自己的刷新间隔和过期时长,例如 Slack 为 1 分钟和 15 分钟,Google 评价为 6 小时和 24 小时。数据源可以选择其他间隔,但不能比预设的最小值更短。同步失败时,卡片会保留最后的数据,直到过期时长结束,然后变为空白。
  • 费用由服务方计收。 Google 评价每刷新一次,就是您 Google Cloud 项目上的一次 Place Details 请求;Google 行程时间每刷新一次,每个目的地就是一个路线元素:四个目的地每 15 分钟刷新,每月约 11,500 个元素。
  • Slack 需要内部应用。 不要分发它:Slack 会把在其 Marketplace 之外分发的应用限制为每分钟一次请求。应用只读取它被添加到的频道,卡片显示的是人们自己发的消息,不包括加入通知、机器人发布的内容或频道通知。

创建数据源

先创建卡片,然后预览,最后保存。

screenrig card create --type events --file events.json
screenrig source test --kind ics --card crd_CARD \
  --set 'calendars=[{"key":"lobby"}]' --set time_zone=America/Vancouver \
  --secret calendar_url.lobby=@lobby.url
screenrig source create --kind ics --card crd_CARD --name "Lobby calendar" \
  --set 'calendars=[{"key":"lobby"}]' --set time_zone=America/Vancouver \
  --secret calendar_url.lobby=@lobby.url
screenrig source show src_SOURCE

source test 会获取该地址,按卡片的需要整理,并用卡片校验结果,不保存也不写入任何内容。它会显示文档、卡片是否接受,以及过程中发现的情况。请通过文件 (@FILE) 或标准输入 (-) 提供地址,使其不会留在 shell 历史记录中。

命令作用
source list, source show ID项目的数据源,附带状态、设置和已遮蔽的密钥
source create保存数据源,并在几秒内运行
source update ID更改名称、卡片、设置(--set KEY=VALUE 或 --settings JSON)、密钥(--secret、--unset-secret)、计划(--refresh 10m),或 --enable 与 --disable
source test预览数据源(无论是否已保存),不写入任何内容
source refresh ID立即同步并显示结果
source delete ID删除数据源;卡片保留其最后一份文档

--settings 以 JSON、@FILE 或 - 的形式接收数据源的完整配置。CLI 的 --config 选项指定的是其凭据文件,与数据源无关。

数据源无法刷新时

数据源写入每份文档时都附带一个截止时间:写入后加上过期时长,并在数据源持续刷新时不断顺延。数据源正常时,截止时间会一直向后移动。数据源失败后,卡片会一直显示最后一次正常的数据直到截止时间,之后数据在所有屏幕(包括离线屏幕)上过期。

  • 设置时长:在 source create 或 source update 上使用 --stale-after 30m,范围 1 分钟到 7 天,且至少为刷新间隔的两倍。--stale-after none 会保留最后一次正常的数据直到下次刷新。场馆预订数据源(momentus)除非另行指定,保留 30 分钟;其他类型保留到下次刷新。
  • 回退到您自己的内容。 visibility.data 条件指向该文档(房间则为 momentus-room-{screen.field.room})的页面在数据存在时显示,数据过期后让位给下一页。请在它之后放一个带有您品牌的普通页面作为备用。
  • 查看。 source show 会列出 stale_after、data_expires_at,并在数据源持续失败且数据已过期时列出 stale: true。仪表板的数据源页面显示同样的内容:失败的数据源显示为“失败中”,数据已过期的显示为“已过期”。
  • 接收通知。 针对条件 source_failed(刷新失败)和 source_stale(数据已过期)的通知,会像针对屏幕一样发送电子邮件,或发布到 Slack、Teams、PagerDuty 或 Telegram:screenrig notifications create --email owner --conditions source_failed,source_stale --hold 5。通知会写明数据源并链接其页面,在刷新成功后解除,并且 --tag 永远不会匹配数据源。

状态与错误

每个数据源都会显示状态:上次运行、上次成功和上次更改卡片的时间,下次运行的时间,连续失败的同步次数,以及最近的错误。

状态含义
pending数据源尚未运行
ok最近一次获取成功,且卡片接受了该文档
error最近一次同步失败;卡片保留最后一份有效文档
disabled计划已停止

ok 不判断上游数据是否正确或最新,也不判断是否有屏幕在显示该卡片。

错误代码发生了什么
unreachablescreenRIG 无法访问该地址。请使用 80 或 443 端口上的公共 http(s) 地址
timeout该地址在 20 秒内没有响应
too_large响应大于 5 MB
http_status该地址返回了 HTTP 错误。404 表示该地址已不再正确
auth_failed该地址拒绝了凭据;下次尝试将等待一小时
not_sharedGoogle 表格没有共享:Google 返回了登录页、401 或 403。请共享给“知道链接的任何人”,或发布到网络
parse_failed响应不是日历。登录页面会被当作网页而不是日历:请使用该日历的 ICS 地址
rate_limited该服务正在限制请求;下次同步将等待
mapping_failed映射无法在消息所指的位置读取响应,例如找不到数组的 rows_path
card_data_invalid卡片在消息所指出的路径处拒绝了该文档
target_missing卡片已被删除。请将数据源指向另一张卡片
payment_required项目存储已满,因此文档没有写入
config_invalid某项设置或已保存的密钥已无法使用。请重新输入
internal同步在 screenRIG 内部失败,将会重试

创建或更改数据源时,可能返回 source_target_taken(该卡片已有数据源)、source_target_unsupported(该类型无法填充这张卡片;响应会列出可以填充的卡片)、source_secret_required(缺少密钥,或密钥是为另一个地址输入的,需要重新输入)、source_limit_reached 和 source_busy(同步正在运行;请在间隔后重试)。当地址无法读取时,source test 会返回 source_test_failed 并附上原因。

日历

日历数据源读取 iCalendar (ICS) 地址:Google 日历、Outlook、iCloud 和 Nextcloud 为日历提供的私密地址,以及任何 webcal:// 链接。它会用所有日历汇总的日程填充 events 卡片,或按每个日历一个房间填充 study-rooms 卡片。

设置含义
calendars1 到 60 个日历,每个为 {key, name, floor, capacity}。键由小写字母、数字、- 和 _ 组成;名称、楼层和容量用于描述一个房间
days从今天起显示多少天,1 到 31(默认 7)
time_zoneIANA 时区,例如 America/Vancouver,用于确定一天的边界,以及订阅源未标注时区的时间
private_title私密事件显示的标题(默认 Private)
include, exclude按标题保留或隐藏事件的关键词:参见按关键词保留或隐藏事件

每个日历有一个密钥 calendar_url.KEY,其中保存它的地址。

  • 重复事件(每日、每周、每月和每年的规则、例外、被移动和被取消的场次)以及时区都在服务器上展开,因此事件在夏令时变化后仍保持原来的时刻,卡片也不会重复日历自身的规则。Outlook 使用的 Windows 时区名称也能识别。
  • 私密事件(CLASS:PRIVATE 或 CONFIDENTIAL)只显示时间,使用私密标题,不显示组织者和地点。该规则只针对标记为私密的事件,不评判其他事件会透露什么。
  • 空闲时间(标记为透明或空闲的事件)会保留在日程中,但会从只计算占用时间的会议室看板中去除。已取消的事件会被丢弃,暂定事件按占用计算。
  • 全天事件显示为日期;没有结束时间的事件不显示结束时间。
  • 警告。 当订阅源未指明时区、使用了 screenRIG 无法识别的时区,或某个事件的重复次数超过 500、事件总数超过 5,000 时,同步仍会成功,状态中会列出警告。
  • 多个日历一起同步:只要有一个失败,整次同步就失败,错误信息会指出是哪个日历,因此看板不会显示不完整的内容。

按关键词保留或隐藏事件

ics、microsoft365、google_calendar 和 momentus 数据源有两个可选设置 include 和 exclude,各是 1 到 20 个关键词的列表,每个关键词 1 到 100 个字符。当事件标题至少包含一个 include 关键词(设置了 include 时),且不包含任何 exclude 关键词时,该事件会保留。关键词在标题内匹配,不区分大小写和重音符号,因此 café 和 CAFE 互相匹配。

  • 仅限标题。 地点、组织者和说明从不参与匹配。没有标题的事件按 Untitled event 匹配。
  • 私密事件按其自身的标题匹配,显示的仍是私密标题。私密标题本身从不参与匹配,所以 exclude: ["private"] 不会移除任何内容。
  • 数据源写入的所有内容都读取过滤后的日历:卡片以及每个按键存储的会议室文档。过滤只移除事件,绝不移除日历或会议室,空闲事件与其他事件同样处理。
  • 状态会统计被排除的数量,记为 filtered,为 0 时也会显示。当时间窗口内有事件而关键词把它们全部排除时,同步仍会成功,并给出提示检查关键词的警告,卡片显示空列表。source test 也会显示同样的内容。
screenrig source create --kind ics --card crd_EVENTS --name "Public events" \
  --set 'calendars=[{"key":"main"}]' --set 'include=["open house","tour"]' --set 'exclude=["cancelled"]' \
  --secret calendar_url.main=@calendar.url

工作表、CSV 和 JSON

sheets、csv 或 json 数据源读取一张表或一个 JSON 文档,并通过映射生成卡片的数据,因此可以填充任何卡片:用价目表生成菜单,用指标表生成 KPI 看板,用积分榜表生成表格。映射是数据源设置中的 JSON 模板。映射生成的内容是否被接受由卡片自己的规则决定,source test 会输出文档并说明卡片是否接受,因此您可以不断修改映射直到被接受。这些数据源每 5 分钟同步一次,最短每分钟一次。

screenrig source test --kind sheets --card crd_MENU --settings @menu-sheet.json
screenrig source create --kind sheets --card crd_MENU --name "Cafe menu" --settings @menu-sheet.json
screenrig source create --kind json --card crd_KPI --name "Daily numbers" \
  --settings @kpi-api.json --secret token=@api.token
设置类型含义
url全部工作表的链接,或 CSV 文件或 JSON 文档的地址:80 或 443 端口上的公共 http(s) 地址
mapping全部生成卡片数据的模板。必填
key_template全部包含 {screen.field.NAME} 占位符的可选键;映射返回由 {fields,document} 组成的 documents 数组
sheetsheets标签页的名称,用于读取链接所指标签页以外的标签页
header_rowsheets, csvtrue(默认)表示第一行是列名;false表示每一行都是数据,列名为 Column 1、Column 2
key_columnsheets, csv其单元格为各行命名的列,使映射可以按名称查找行
delimitercsv,、; 或 tab。默认使用第一行中出现最多的那个
authcsv, jsonnone(默认)、bearer、header、basic、oauth2 或 token_request:参见请求、登录和分页
header_namecsv, json当 auth 为 header 时,承载令牌的标头,例如 X-API-Key
rows_pathjson行数组在响应中的位置,例如 data.items
key_fieldjson每一行中为其命名的成员。请与 rows_path 一起设置

共享 Google 表格

sheets 数据源通过 Google 的 CSV 导出读取工作表。它不需要登录,也不需要访问您的 Google 账号,但工作表必须允许任何拿到链接的人读取。

  1. 在 Google 表格中选择共享,将常规访问权限设为“知道链接的任何人”,角色设为“查看者”。若要改为发布,请选择文件、共享、发布到网络。
  2. 将浏览器地址栏中的链接复制到 url。指向某个标签页的链接会保留该标签页。若要按名称读取其他标签页,请设置 sheet;已发布的链接不接受 sheet。
  3. 用 source test 预览。工作表的表头行会成为映射读取的列键。

未共享的工作表会返回登录页、401 或 403。此时状态为 not_shared,消息会指出需要更改的设置:共享、常规访问权限、知道链接的任何人、查看者,或者文件、共享、发布到网络。请让工作表的所有者共享它。not_shared 只判断这三种响应,不会将列与您的映射进行比较。

需要令牌的地址

公共地址上的 CSV 文件或 JSON 文档不需要令牌。对于受保护的地址,请将 auth 设为 bearer(screenRIG 发送 Authorization: Bearer TOKEN),或设为 header 并配合 header_name(发送 HEADER_NAME: TOKEN),并将令牌作为密钥 token 提供:

screenrig source create --kind csv --card crd_TABLE --name "Standings" \
  --settings @standings.json --secret token=@api.token

令牌以加密方式存储,只以提示形式返回:有 12 个或更多字符时为最后四个字符,否则为 …。它只会发送到地址自身的来源,即协议、主机和端口。重定向到其他站点时不会携带令牌,未重新输入令牌就把数据源指向另一个来源会被拒绝,并返回 source_secret_required。只更改路径或查询参数时会保留令牌。关于 basic、OAuth 2 和令牌请求,以及带正文、请求头或分页的请求,参见请求、登录和分页。header_name 不能是 Host、Cookie、Content-Length、Connection、Transfer-Encoding、Upgrade、Proxy- 标头,也不能是 screenRIG 自己设置的标头。

请求、登录和分页

csv 或 json 数据源会向其 url 发送 GET 请求。这些设置可以发送正文或额外的请求头、登录、读取多页,并把今天的日期放进请求。每个请求有 20 秒,整次运行有 60 秒。

设置含义
methodGET(默认)或 POST。有 body 或 graphql 时会变成 POST
bodyJSON 对象或数组,或原样发送的文本。占位符填入后最多 16 KiB
body_formatjson(默认)或 form。表单正文是由文本、数字和布尔字段组成的对象,或类似 a=1&b={{today}} 的文本;值会进行 URL 编码
graphql{query, variables},以 JSON POST 发送。有错误且没有数据的响应是 parse_failed;数据旁带有错误只是警告
headers最多 8 个值不是密钥的额外请求头。Authorization 会被拒绝,header_name 拒绝的任何请求头也一样:请使用 auth 或请求头密钥
time_zone日期占位符使用的 IANA 时区。未设置时按 UTC 读取,状态中会列出警告
pagination读取多页,详见下文
format仅限 json 类型:auto(默认)、json、csv 或 xml

名为 header.NAME 的密钥会作为请求头 NAME 发送,因此有两个凭据请求头的 API 会使用 header.X-Client-Id 和 header.X-Api-Key。名为 query.NAME 的密钥会以 NAME=value 的形式添加到地址中。每种最多 8 个,两种都加密封存且不会显示,预览中查询密钥显示为 NAME=***。

身份验证设置和密钥
none默认值
bearer密钥 token,以 Authorization: Bearer 发送
headerheader_name 和密钥 token
basicusername(不含冒号)和密钥 password,以 Authorization: Basic 发送
oauth2客户端凭据:token_url、client_id、可选的 scope 和 audience、token_auth(body 为默认值,或 basic)以及密钥 client_secret。screenRIG 向 token_url 请求令牌,并以 Authorization: Bearer 发送
token_request返回令牌的登录:token_url、token_body(其字符串可把已存储的密钥读取为 {{secrets.NAME}} 的对象)、token_body_format(json 或 form)、token_path(令牌在响应中的位置,如 token.accessToken),以及可选的 token_expires_path、token_ttl(60 到 86,400 秒,默认 3,600 秒)和 header_name(原样携带令牌的请求头;不设置时为 Authorization: Bearer)

screenRIG 只在内存中保留令牌,绝不写入存储,并在其过期前一分钟续期。地址以 401 拒绝令牌时,它会重新请求一次新令牌。令牌地址的拒绝(HTTP 400、401 或 403)是 auth_failed,令牌地址返回的任何内容都不会被引用。

{
  "url": "https://api.example.com/v2/orders/summary",
  "method": "POST",
  "body": {"from": "{{today-7d}}", "to": "{{today}}"},
  "time_zone": "America/Vancouver",
  "auth": "oauth2",
  "token_url": "https://login.example.com/oauth/token",
  "client_id": "screen-signs",
  "scope": "orders.read",
  "mapping": {"body": "{{body.total}} orders this week"}
}
screenrig source create --kind json --card crd_BOARD --name "Orders" \
  --settings @orders.json --secret client_secret=@client.secret

日期占位符可填入地址、正文和 headers:{{now}}(2026-11-01T17:30:00Z)、{{today}}(2026-11-01)和 {{unix}}(秒),都可带可选偏移量,如 {{now-90m}}、{{now+7d}} 或 {{today-1d}}(单位为 m、h 和 d;{{today}} 只接受天)。它们按 time_zone 读取,未设置则按 UTC。每次运行都会根据时钟填入,因此明天的运行请求的就是明天的日期。

分页。 pagination 是 {type, max_pages},其中 max_pages 为 1 到 20(默认 5)。json 数据源需设置 rows_path,使各页的行合并到同一个数组,csv 数据源则合并表的行。

类型如何找到下一页
link_headerLink 头中的 rel="next" 地址
next_urlpath,响应中存放下一个地址的位置(仅 json)
cursorparam 和 path:游标以 param 放入地址,或用 "in": "body" 放入正文,游标缺失或为空时读取结束(仅 json)
pageparam、start(默认 1)以及带 size 的 size_param:页码放入地址,或用 "in": "body" 放入正文,读到没有行或少于 size 行的页面时结束

下一个地址必须停留在数据源自己的来源,因此密钥绝不会离开它。重复的页面,或超过 max_pages 的页数,会让读取带着警告结束,所有页面合计最多 5 MB。

XML 和 RSS。 以 < 开头的响应,或设置了 format xml 的响应,会按 XML 读取。body 保存根元素:属性为 @name,含子元素的元素文本为 #text,重复的元素是列表,名称保留其前缀(item["media:thumbnail"]["@url"])。RSS 或 Atom feed 还会得到 body.items,每个条目在有的情况下带有 title、link、published、summary(纯文本,最多 1,000 个字符)和 image。DOCTYPE 绝不会被跟随,也不会从中获取任何内容。

source test 会在 request 中显示发出的第一个请求:已填入的占位符、以 NAME=*** 表示的查询密钥、Accept 和 Content-Type 之外的请求头名称(不含值)、正文的前 1 KiB 以及读取的页数。

数据源读取什么

  • CSV 与工作表。普通 CSV:字节顺序标记、带引号的字段、成对的引号、引号内的换行以及 CRLF 行尾都能读取。单元格会去掉首尾空白,没有内容的行会被丢弃。表头会变成小写、去掉重音的键,其他字符的每一段连续字符变成一个 _,因此 Sold out 变为 sold_out;空表头变为 column_3(即其位置),重复的变为 _2、_3。不是 UTF-8 文本的文件(例如电子表格文件)会得到 parse_failed:请将其另存为 UTF-8 的 CSV。
  • JSON。数字保留原有的位数。设置 rows_path 后,数据源会在那里查找行数组;找不到数组的路径会在 rows_path 处得到 mapping_failed,空数组则是没有行的表。
  • 限制。表最多读取 10,000 行和 200 列。超出部分同步仍会成功,状态会列出一条警告,说明省略了多少行或列。
  • 如果该地址返回的是网页而不是数据,结果为 parse_failed。

映射

映射是一个 JSON 模板。包含 {{item.price}} 这类占位符的字符串是文本,恰好只含一个占位符的字符串会保留值的类型。内容为空的路径在文本中为空,作为单独的值则被省略,卡片随后会指出缺少什么。路径可以从哪里开始取决于类型:

类型起点包含
sheets, csvrows每一行数据,按列键组成的对象
sheets, csvcolumns[{key, name}],按工作表顺序
sheets, csvby_key有 key_column 时:按该列单元格去掉首尾空白的文本索引各行
jsonbody整个响应
jsonby_key有 rows_path 和 key_field 时:按该成员去掉首尾空白的文本、数字或布尔值索引各行

请优先使用 by_key,而不是 rows[2] 这样的位置:通过键找到的行,在有人给表排序或在上方插入行后仍然能找到。带空格的键写作 by_key["Net sales"].value。如果两行共用一个键,第一行优先,状态会列出一条警告,统计重复的数量。任何警告都不会引用单元格内容。

单元格是文本,因此需要转换:{"$number": "{{item.price}}"} 可读取 1,234.50、$4.50、3,50 € 和 12%(用逗号作小数点时加上 "$decimal": ","),{"$bool": "{{item.sold_out}}"} 可读取 true、yes、y、1 和 x。$each 遍历列表,并提供 item 和 index;$group 配合 $by 按首次出现的顺序对行分组,并提供 group.key 和 group.items;$text 配合 $max 截断文本。模板会在保存数据源时检查。

用包含 Section、Item、Price 和 Sold out 列的表生成的菜单会按分区对行分组:

{
  "url": "https://docs.google.com/spreadsheets/d/SHEET_ID/edit#gid=0",
  "mapping": {
    "sections": {
      "$group": "{{rows}}", "$by": "{{item.section}}",
      "$template": {
        "name": "{{group.key}}",
        "items": {
          "$each": "{{group.items}}",
          "$template": {
            "name": "{{item.item}}",
            "price": { "$number": "{{item.price}}" },
            "soldOut": { "$bool": "{{item.sold_out}}" }
          }
        }
      }
    }
  }
}

用包含 Metric、Value、Unit 和 Change 列的表生成的 KPI 看板按名称查找每个指标,因此对表排序不会移动任何内容:

{
  "url": "https://docs.google.com/spreadsheets/d/SHEET_ID/edit#gid=0",
  "key_column": "Metric",
  "mapping": {
    "tiles": [
      { "label": "Revenue", "value": { "$number": "{{by_key.Revenue.value}}" },
        "unit": "{{by_key.Revenue.unit}}", "delta": { "$number": "{{by_key.Revenue.change}}" } },
      { "label": "Orders", "value": { "$number": "{{by_key.Orders.value}}" },
        "delta": { "$number": "{{by_key.Orders.change}}" } }
    ]
  }
}

表格卡片会列出每一行:

{
  "mapping": {
    "rows": {
      "$each": "{{rows}}", "$limit": 50,
      "$template": {
        "team": "{{item.team}}",
        "points": { "$number": "{{item.points}}" },
        "played": { "$number": "{{item.played}}" }
      }
    }
  }
}

要查看数据源读到了什么,请用映射 {"columns": "{{columns}}", "rows": "{{rows}}"} 测试。卡片会拒绝该文档,但预览仍会输出它,包含每个键和每一行。

这些数据源上的 ok 表示地址已响应,且卡片接受了映射生成的文档。它并不表示工作表或响应包含映射所期望的行:消失的列会使其成员被省略,缺少必需成员的文档会被卡片拒绝。

计算值

映射还可以在服务器上计算真假值和数字,因此只测试相等的页面条件(如带 equals: true 的 visibility.data)也能跟随数字变化。卡片 webhook 和叠加层触发器的映射也有同样的辅助函数。

函数结果写法
$gt, $gte, $lt, $lte真或假,两个值都按数字读取{"$lt": ["{{body.stock}}", 5]}
$between当值在两个边界之内(含边界)时为真{"$between": ["{{body.temp}}", 18, 24]}
$eq, $ne数字按值比较(5、"5" 和 "5.00" 相等),其他内容按精确文本比较,区分大小写{"$eq": ["{{body.status}}", "open"]}
$contains文本包含某段文本(忽略大小写),或列表包含某个值{"$contains": ["{{body.title}}", "closed"]}
$in值是列表中的一项{"$in": ["{{body.tier}}", ["gold", "silver"]]}
$and, $or, $not组合条件{"$and": [{"$gt": ["{{body.a}}", 1]}, {"$not": {"$eq": ["{{body.s}}", "x"]}}]}
$sum, $avg, $min, $max来自列表的一个数字{"$sum": "{{rows}}", "$of": "{{item.amount}}"}
$count有多少元素,或有多少元素通过 $where{"$count": "{{body.queue}}", "$where": {"$gt": ["{{item.wait}}", 5]}}
  • 无法进行的比较为假,并给出指明位置而从不显示值的警告,因此缺失、为空或非数字的值绝不会让页面显示。$bool 是唯一把缺失值当作假的读取器。
  • 列表。 $of 从每个元素读取一个占位符,可使用 item 和 index,$where 保留满足条件的元素。为空或不是数字的元素会被跳过,并由一条警告统一计数。没有可相加的内容时,$sum 和 $count 为 0,$avg、$min 和 $max 会省略其成员。
  • 数字。 使用逗号作小数点的值(3,50、1.234,50)需要 "$decimal": ","。$avg 四舍五入到 2 位小数,"$round" 可设置 0 到 6 位。$sum、$min 和 $max 是精确值。

来自 JSON 响应的低库存标记。当页面的 visibility.data 用 path /low_stock 和 equals true 指向该文档时,该页面只在库存偏低时显示:

{
  "sku": "{{body.sku}}",
  "stock": {"$number": "{{body.stock}}"},
  "low_stock": {"$lt": ["{{body.stock}}", 5]}
}

来自表格的、低于各自补货水平的任意商品:

{"low_stock": {"$gt": [{"$count": "{{rows}}", "$where": {"$lt": ["{{item.stock}}", "{{item.reorder_at}}"]}}, 0]}}

按键存储的文档

JSON、CSV 和 Sheets 源接受 key_template,例如 room-{screen.field.room}。将数据源映射为由 {"fields":{"room":"A101"},"document":{"body":"Room A101"}} 对象组成的 documents 数组。卡片 Webhook 使用 --key-template 生成相同的输出,也可以不经映射直接接收外层对象。

{
  "key_template": "room-{screen.field.room}",
  "mapping": {
    "documents": {
      "$each": "{{rows}}",
      "$template": {
        "fields": {"room": "{{item.room}}"},
        "document": {"body": "{{item.title}}"}
      }
    }
  }
}

放置所属的 crd_ 实例并设置 data: "room-{screen.field.room}";每个屏幕只读取由自身 room 字段生成的键。所有输出都使用实例的卡片架构。同一数据源供不同类型的卡片使用时,请分别设置实例、映射和前缀。

使用 source test 预览。无效文档、缺失字段和重复键会在写入前导致整批被拒绝。存储故障可能留下部分已写入的文档:请使用相同的 Idempotency-Key 重发完全相同的 Webhook 正文,或刷新源。被省略的键保留原有数据和到期时间;Webhook 不设置到期时间。使用 --clear-key-template 清除 Webhook 的模板。

文件和表格的小技巧

  • OneDrive 和 SharePoint。 共享链接会打开预览页面,数据源会把它当作网页读取(parse_failed)。在 CSV 文件的链接后加上 ?download=1,如果链接已有 ?,则加 &download=1,这样链接就会直接返回文件。链接必须无需登录即可使用。
  • 按名称查找行。 把 key_column 设为给每一行命名的列(按其表头,不区分大小写),并把行读取为 by_key.NAME.column:对表格排序或插入行不会改变任何内容。与任何列都不匹配的名称会在 key_column 处产生 mapping_failed,重复的键会保留第一行,并给出统计重复数的警告。

会议室

会议室数据源读取会议室日历,并填充 room 卡片(一间会议室的门牌)或 room-board 卡片(一栋楼的所有会议室)。它也可以填充 events 日程或 study-rooms 卡片,日历地址(ICS)同样可以填充这些卡片。

卡片根据所持有的预订,每秒在屏幕上算出会议室是空闲、占用还是接下来是什么,因此即使屏幕离线,门牌也始终正确。会议室数据源只读取:卡片只显示预订,不接受输入,所以不会从屏幕上预订、结束或签到。

设置含义
rooms1 到 60 间会议室,每间包含一个 key(小写字母、数字、- 和 _)、会议室的地址(Microsoft 365 用 email,Google 用 calendar_id),以及可选的 name、floor 和 capacity。room 卡片只接受一间会议室
days从今天起显示多少天,1 到 31(默认 7)
time_zoneIANA 时区,例如 America/Los_Angeles,用于确定每天的界限和全天事件。不设置时,每天从 UTC 午夜开始,状态会列出一条警告
private_title私密事件显示的标题(默认 Private)

Microsoft 365 会议室邮箱

类型 microsoft365 通过您自己的 Microsoft Entra 应用读取 Exchange Online 会议室邮箱,因此应用、它的密钥以及它能读取的内容都由您掌控。另有两项设置:tenant,即目录 ID 或已验证的域名,例如 contoso.com(common、organizations 和 consumers 会被拒绝);client_id,即应用的应用程序(客户端)ID。密钥 client_secret 是应用的客户端密钥的值。

  1. 在 Microsoft Entra 管理中心注册一个不带重定向 URI 的单租户应用,添加客户端密钥(复制其值,而不是密钥 ID),添加 Microsoft Graph 应用程序权限 Calendars.Read,并授予管理员同意。
  2. 把应用限制在会议室邮箱范围内。在应用程序访问策略加以限制之前,Calendars.Read 可访问组织内的每个邮箱。把会议室放进启用邮件的安全组,然后在 Exchange Online PowerShell 中运行下面的前两条命令;对于组内的会议室,Test-ApplicationAccessPolicy 会返回 Granted。Exchange 的应用程序 RBAC 也能以同样方式限制应用。
  3. 除非另行设置,会议室邮箱会把会议主题替换为组织者的姓名。若要显示会议标题,请对每间会议室运行第三条命令。
  4. 先预览,再保存。source test 会登录并读取每间会议室,报告读取到的会议室和预订、每间会议室的数量;如果应用还拥有 Place.Read.All,还会报告组织列出的会议室数量。
# Exchange Online PowerShell
New-ApplicationAccessPolicy -AppId CLIENT_ID -PolicyScopeGroupId rooms@example.com -AccessRight RestrictAccess -Description "screenRIG room signs"
Test-ApplicationAccessPolicy -AppId CLIENT_ID -Identity boardroom@example.com
Set-CalendarProcessing -Identity boardroom@example.com -DeleteSubject $false -AddOrganizerToSubject $false

# screenRIG (source create takes the same options, plus --name)
screenrig source test --kind microsoft365 --card crd_ROOM \
  --set tenant=contoso.com --set client_id=CLIENT_ID --set time_zone=America/Los_Angeles \
  --set 'rooms=[{"key":"boardroom","email":"boardroom@example.com","name":"Boardroom","floor":"Level 4","capacity":12}]' \
  --secret client_secret=@secret.txt
  • 映射。私密和机密会议显示私密标题,不显示组织者。标记为空闲或在其他地点办公的会议,以及会议室已拒绝的会议,视为空闲时间;暂定会议计为占用;已取消的会议会被丢弃。全天事件是会议室所在时区的日期。
  • 频率。会议室数据源每 2 分钟同步一次。登录约一小时有效,并会重复使用。
  • 错误。会引用 Microsoft 返回的错误代码和文本,例如客户端密钥错误时的 AADSTS7000215;应用无法读取的会议室会使同步失败,并在错误中点名该会议室。
  • 改用连接。 把 connection_id 设为项目的 Microsoft 连接(两种模式均可),以代替 tenant、client_id 和 client_secret。这样数据源不保存任何密钥,并按连接的方式登录。

Google Workspace 资源日历

类型 google_calendar 通过您自己的 Google 服务账号读取资源日历和会议室日历。密钥 service_account_key 是 Google 签发的原样密钥文件,其提示是账号的地址。可选设置 subject 指定在全网域授权下该账号所代表的用户。

  1. 在 Google Cloud 中启用 Google Calendar API,创建服务账号,并为其下载 JSON 密钥。
  2. 让账号可以访问这些会议室。要么把每个会议室日历共享给该账号的地址,并授予“查看所有活动详情”权限;要么在管理控制台中为该账号启用全网域授权,范围为 https://www.googleapis.com/auth/calendar.events.readonly,并把 subject 设为能看到这些会议室的用户。
  3. 会议室的 calendar_id 是该资源在管理控制台“建筑物和资源”下的日历 ID,以 @resource.calendar.google.com 结尾。先用 source test 预览,再保存。
screenrig source test --kind google_calendar --card crd_ROOM \
  --set time_zone=America/Los_Angeles \
  --set 'rooms=[{"key":"boardroom","calendar_id":"CALENDAR_ID","name":"Boardroom"}]' \
  --secret service_account_key=@key.json
  • 映射。私密和机密事件显示私密标题,不显示组织者。标记为透明的事件以及会议室已拒绝的事件视为空闲时间,已取消的事件会被丢弃。全天事件保留其日期。
  • 错误。会引用 Google 返回的错误文本。账号看不到的日历会在错误中点名,并说明如何共享。
  • 改用连接。 把 connection_id 设为项目的 Google 连接,以代替 service_account_key。使用 screenRIG 的应用时,数据源读取批准它的账号能看到的日历,因此该账号必须能看到数据源列出的每个会议室日历,并且不允许使用 subject。

限制与隐私

  • room 卡片保留最早的 200 条预订,room-board 卡片每间会议室保留 60 条。超出时,状态会列出一条警告,说明有多少条被舍弃。
  • 卡片的文档只包含卡片会绘制的内容,因为每个显示卡片的屏幕都会保留其文档。room 卡片只有在 showOrganizer 开启时才会收到组织者。room-board 卡片从不会收到组织者,并且除非 showTitles 开启,否则会收到私密标题以代替每个会议标题。对这两项设置的更改会在下一次同步时到达卡片。私密会议在两种情况下都使用私密标题。
  • 这些类型的消息会引用 Microsoft 或 Google 返回的错误文本,绝不会包含密钥。

场馆的预订系统

类型 momentus 使用您的管理员在 Momentus 中创建的 API 用户(Main Menu,Api Users),读取 Momentus Enterprise(以前以 Ungerboeck 和 EBMS 名称销售的系统)的活动安排(房间的每次使用及其日期和时间)和空间。它可以填充活动目录的 events 卡片、大厅或走廊的 room-board 卡片,或单个房间的 room 卡片;为每个房间写入一份文档后,每个屏幕都显示自己房间的预订。活动前不久发生变更的房间会在刷新间隔(默认一分钟)内显示。

设置含义
base_url您的 Momentus Web 服务器的 https 地址,例如 https://momentus.example.com/prod;API 从 {base_url}/api/v1/... 读取
organization读取数据所用的 Momentus 组织代码
api_userAPI 用户的 ID
time_zone场馆的 IANA 时区。Momentus 保存的是本地日期和时间,因此请务必设置;未设置时,时间按 UTC 读取,状态会列出警告
days从今天起读取多少天,1 到 31(默认 7)
rooms{key, space, name?, floor?, capacity?, area?} 的可选列表;space 是 Momentus 空间代码。不设置时,Momentus 列出的每个空间都是一个房间(键为小写的代码)
space_filter, function_filter可选的 Momentus 搜索表达式,用于限定读取的空间或活动安排
exclude_status_codes要排除的活动安排状态代码,例如已取消的。状态代码由您的场馆自行定义,因此没有默认值
include, exclude按数据源显示的标题保留或隐藏功能的关键词,与日历相同

两个密文:api_key 是 API 用户的一个密钥 GUID,api_secret 是其密文 GUID。两者都加密保存,不会再次显示;如果数据源指向其他服务器、组织或 API 用户,需要重新输入。

screenrig source test --kind momentus --card crd_EVENTS \
  --set base_url=https://momentus.example.com/prod --set organization=10 \
  --set api_user=venue-api --set time_zone=America/Los_Angeles \
  --set 'exclude_status_codes=["XX"]' \
  --secret api_key=@key.guid --secret api_secret=@secret.guid
screenrig source create --kind momentus --card crd_EVENTS --name "Venue events" \
  --settings @momentus.json --secret api_key=@key.guid --secret api_secret=@secret.guid

source test 会读取 Momentus,整理文档并对照卡片检查,然后显示读取到的房间和活动(functions、functions_other_spaces、events_KEY)。不会保存或写入任何内容。

  • 活动安排如何转换。 活动安排的 Space 决定房间,Description 作为标题(title_field 可指定其备用描述之一),房间名称作为地点。没有开始时间的活动安排是全天活动。跨多个日期且带时间的活动安排会在每个日期显示这些时间;结束早于开始的则跨夜进行。
  • 私密标题。 除非开启 show_titles,否则 room-board 显示私密标题,与其他房间类型相同。
  • 错误。 由于服务器是您自己的,Momentus 自身的错误文本不会被引用:auth_failed 表示 API 用户、密钥或密文有误,或无权读取活动安排和空间;config_invalid 表示筛选条件不是有效的 Momentus 搜索;parse_failed 表示 base_url 指向的是网页而不是 API。

每个房间一块标牌

设置 room_key_prefix(例如 momentus-room-)后,数据源还会按该前缀加房间键,为每个房间写入一份 room 文档。为每个房间的屏幕设置其房间,并放置一张 room 卡片,其 data 键为该前缀加屏幕自己的字段:

screenrig screen update scr_BALLROOM --field room=ballroom-a

在此播放列表图元中使用内联目录类型 room:

{"id":"door","primitive":"card","card":"room","layout":"full","content_fit":"fill","params":{"room":"{screen.field.room}"},"data":"momentus-room-{screen.field.room}"}

crd_ 图元使用实例自身的 params 和 major,请省略这些字段。可选的 data 用于选择文档键或每屏幕键模板。源的 --card crd_ID 仍指定其同步目标。对于页面,将 visibility.data.key 设为 momentus-room-{screen.field.room},并在其后添加一个无条件的备用页面。

后端会在每个屏幕的清单中填入该字段,因此一份播放列表即可让每个屏幕显示自己的房间;没有房间字段的屏幕在该卡片上不显示任何内容。area_key_prefix 对走廊屏幕同样适用:每个房间的 area 将其分组,该区域房间的 room-board 会按前缀加区域键写入(area_card 可以是 events)。screen_field 和 area_field 指定下方检查所读取的屏幕字段(默认分别为 room 和 area)。

检查映射

screenrig source rooms src_ID(以及仪表板中数据源页面的“房间映射”部分)会列出数据源提供但没有任何屏幕显示的房间,以及房间缺失或指定了 Momentus 中不存在的房间的屏幕:

screenrig source rooms src_VENUE

当屏幕在该字段上有值,或屏幕自己的播放列表放置了读取该键的卡片时,它会出现在报告中。每个屏幕的状态为 mapped、missing(播放列表读取该字段而屏幕没有值)或 unknown(值没有指向任何房间;报告会指出可能想指的房间,例如仅大小写不同)。使用 screenrig screen update scr_ID --field room=ROOM_KEY 修正屏幕。报告读取数据源最后一次成功刷新时的房间,不会获取任何内容;列出 rooms 的数据源在首次刷新之前就已知道这些房间。

安全

能授予访问权限的地址就是凭据。screenRIG 会加密存储它,只回显协议和主机,并且仅在运行同步时使用。数据源只读取 80 或 443 端口上的公共 http(s) 地址,最多跟随三次重定向;无法解析到公共主机的地址、被拒绝的连接和握手失败看起来完全一样,因此无法利用数据源探测网络。任何消息都不会引用地址或收到的响应。数据源只读取:绝不会写入日历。