Compare commits

...
106 Commits
Author SHA1 Message Date
Owncast 11af286501 Bundle embedded web app 2025-01-23 03:56:45 +00:00
613d928149 Updated translations (#4176)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-22 19:51:53 -08:00
renovate[bot]andGitHub 7ecc7d33d4 chore(deps): update peter-evans/create-or-update-comment digest to 2ef7be1 2025-01-22 06:37:22 +00:00
Owncast 9c8c18919f Bundle embedded web app 2025-01-22 01:40:34 +00:00
69fd525b8d Updated translations (#4169)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-21 17:35:40 -08:00
Owncast 1df783d0ff Bundle embedded web app 2025-01-21 23:25:56 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8a5afae48a chore(deps): bump undici from 6.21.0 to 6.21.1 in /web (#4173)
Bumps [undici](https://github.com/nodejs/undici) from 6.21.0 to 6.21.1.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.21.0...v6.21.1)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-21 15:20:48 -08:00
Owncast 15e0a2eb73 Bundle embedded web app 2025-01-21 07:34:12 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
bf685c5702 fix(deps): update dependency yaml to v2.7.0 (#4172)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 23:29:02 -08:00
Owncast d1fd6bc8cb Bundle embedded web app 2025-01-21 07:21:16 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
6c8daab1bd chore(deps): update dependency typescript to v5.7.3 (#4171)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 23:16:05 -08:00
Owncast 3e1f06913f Bundle embedded web app 2025-01-21 07:12:47 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
1e80c6dff8 chore(deps): update dependency chromatic to v11.25.0 (#4170)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 23:07:28 -08:00
Owncast 6482fc9f59 Bundle embedded web app 2025-01-21 03:45:30 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
31d6fc94fe chore(deps): update dependency eslint-plugin-storybook to v0.11.2 (#4165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 19:40:22 -08:00
Owncast 8e89c71a34 Bundle embedded web app 2025-01-21 03:24:24 +00:00
1ad9376c4b Updated translations (#4168)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-20 19:19:37 -08:00
Owncast 3ddc5a8d50 Bundle embedded web app 2025-01-21 02:49:11 +00:00
6fd3aad81d Updated translations (#4162)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-20 18:44:02 -08:00
Owncast 520183c39f Bundle embedded web app 2025-01-21 02:07:32 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
86078d8b90 chore(deps): update dependency knip to v5.42.2 (#4166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 18:02:35 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
caa27d6fdb chore(deps): update dependency sass to v1.83.4 (#4167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 18:01:32 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
24b1dd2ed8 chore(deps): update dependency @types/node to v22.10.7 (#4164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 18:01:03 -08:00
Gabe KangasandGitHub c1f4096e11 chore(go): new chat message db repository. Closes #3081 (#4161) 2025-01-20 16:32:25 -08:00
Owncast db0ddfe009 Bundle embedded web app 2025-01-21 00:04:05 +00:00
9ea94e9963 Updated translations (#4160)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-20 15:58:45 -08:00
Owncast c72ae603c1 Bundle embedded web app 2025-01-20 20:48:24 +00:00
5e8373fcac Updated translations (#4156)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-20 12:43:32 -08:00
Owncast b48b3cbc35 Bundle embedded web app 2025-01-20 02:57:04 +00:00
515e378b4b Updated translations (#4153)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-19 18:52:08 -08:00
Owncast 583668f750 Bundle embedded web app 2025-01-19 23:20:23 +00:00
d109c0cd7d Updated translations (#4152)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-19 15:15:29 -08:00
Owncast 7949670061 Bundle embedded web app 2025-01-19 22:07:46 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
9a7a072050 chore(deps): update dependency eslint-plugin-react to v7.37.4 (#4144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-19 14:02:25 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
eb608414f6 chore(deps): update dependency eslint-plugin-prettier to v5.2.2 (#4143)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-19 14:00:57 -08:00
Owncast 0d38420a7a Bundle embedded web app 2025-01-19 21:50:27 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
d81c148f68 chore(deps): update dependency stylelint to v16.13.2 (#4145)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-19 13:45:23 -08:00
Owncast 89b4e39542 Bundle embedded web app 2025-01-19 21:45:08 +00:00
4ad771ac3d Updated translations (#4151)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-19 13:40:21 -08:00
Gabe Kangas 0e8ff0bea3 chore(i18n): update translation commit message 2025-01-19 13:31:20 -08:00
Gabe Kangas ce976a5f0b chore(ci): tweak i18n workflows 2025-01-19 13:20:32 -08:00
Owncast 5e64b6ea41 Bundle embedded web app 2025-01-19 20:20:54 +00:00
49f7c12b7e Updated translations (#4148)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-19 12:15:38 -08:00
Vibhanshu JainandGitHub 9dcee79432 Update translation.json (#4150) 2025-01-19 12:10:58 -08:00
Gabe Kangas e78d62ce63 chore(go): move stream keys to use generated type. For #3778 2025-01-18 16:38:59 -08:00
Gabe Kangas b3947ef7ea chore(go): move a couple more handlers to use generated types. For #3778 2025-01-18 16:24:35 -08:00
Gabe KangasandGitHub 6abbf8f50c chore(go): create webhooks repository. Closes #4085 (#4146) 2025-01-18 15:40:10 -08:00
Owncast da9d5b8411 Bundle embedded web app 2025-01-18 18:18:07 +00:00
05dd162de5 Updated translations (#4141)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-18 10:12:47 -08:00
Owncast fc862b3fa0 Bundle embedded web app 2025-01-18 17:18:19 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
6f8e9f9496 chore(deps): update dependency emoji-mart to v5.6.0 (#4139)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-18 09:13:09 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
d6d126a874 chore(deps): update dependency chromatic to v11.24.0 (#4138)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-18 09:11:50 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
555b305405 chore(deps): update dependency knip to v5.42.1 (#4140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-18 09:11:29 -08:00
Owncast 58b13d3355 Bundle embedded web app 2025-01-18 08:58:40 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ff45f75731 fix(deps): update codemirror (#4136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-18 00:53:42 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
d9c97fb982 fix(deps): update dependency react-error-boundary to v5 (#4137)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-18 00:53:16 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3ab5702741 chore(deps): update dependency less to v4.2.1 (#4135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-18 00:52:38 -08:00
Owncast cc43d14199 Bundle embedded web app 2025-01-18 00:01:26 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9c243f0ddf chore(deps): bump katex from 0.16.19 to 0.16.21 in /web (#4132)
Bumps [katex](https://github.com/KaTeX/KaTeX) from 0.16.19 to 0.16.21.
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.16.19...v0.16.21)

---
updated-dependencies:
- dependency-name: katex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-17 15:56:19 -08:00
Owncast 4b39458bf1 Bundle embedded web app 2025-01-17 23:46:08 +00:00
Gabe Kangas 47857e283e fix(web): improve sizing and spacing of offline embed. Closes #4133 2025-01-17 15:40:06 -08:00
Owncast 6017d575c8 Bundle embedded web app 2025-01-17 19:58:35 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
536eeb804a fix(deps): update dependency @uiw/codemirror-theme-bbedit to v4.23.7 (#4122)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-17 11:53:22 -08:00
Tianhao WangandGitHub 5c5e28a09c chore: remove binary blob (#4130)
I guess the .DS_Store is included by accident.. Let's remove it before it's
checked into a release tag.

Signed-off-by: Tianhao Wang <shrik3@mailbox.org>
2025-01-17 10:22:08 -08:00
Owncast 4690b13d6b Bundle embedded web app 2025-01-17 06:11:32 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
78eec1e021 fix(deps): update dependency @uiw/react-codemirror to v4.23.7 (#4123)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 22:06:11 -08:00
Owncast 244dc2d7c1 Bundle embedded web app 2025-01-17 05:16:06 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
03bac4ab48 fix(deps): update dependency react-markdown to v9.0.3 (#4125)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 21:11:00 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2d4ae6ca20 chore(deps): update dependency @types/react to v18.3.18 (#4124)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 21:10:26 -08:00
Owncast 2a4f53eb54 Bundle embedded web app 2025-01-17 03:51:09 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
d494856ca2 chore(deps): update dependency @types/prop-types to v15.7.14 (#4121)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 19:44:34 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
e8a5ff95d4 fix(deps): update dependency react-virtuoso to v4.12.3 (#4126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 19:42:42 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
a282daa12f fix(deps): update dependency ua-parser-js to v1.0.40 (#4127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 19:41:49 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2ff4baeb18 chore(deps): update dependency @types/node to v22.10.6 (#4128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 19:40:47 -08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
96ae5c7dce fix(deps): update module github.com/aws/aws-sdk-go to v1.55.6 (#4116)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 14:33:41 +00:00
Owncast d328fe1fd8 Bundle embedded web app 2025-01-16 07:40:52 +00:00
Gabe Kangas 8ab8659889 fix(i18n): next-export-i18n should be using the 2.x.x branch 2025-01-15 23:32:16 -08:00
Owncast 5733c86d8f Bundle embedded web app 2025-01-16 06:09:16 +00:00
Owncast a1d94a7f99 Commit updated API documentation 2025-01-16 06:05:39 +00:00
Gabe Kangas c1366518ad chore: bump version number 2025-01-15 22:02:48 -08:00
Gabe Kangas bd8dc8326c fix(admin): do not log newfeed 404s 2025-01-15 22:00:32 -08:00
Gabe Kangas d03cac106c fix(admin): do not handle version upgrade logic if current version is not set 2025-01-15 21:59:45 -08:00
Owncast 1c69164a72 Bundle embedded web app 2025-01-16 00:20:30 +00:00
2e8e61309a Remove use of default props (#4118)
* refactor: replace defaultProps with function parameters in Modal, Statusbar, ChatContainer, and CrossfadeImage components

* New commit for Default properties of React components after syncing fork and rebasing

* fix: fix linter warning

---------

Co-authored-by: swarup <swarupnarkhede999@gmail.com>
2025-01-15 16:12:54 -08:00
Owncast 67ef2b45d9 Bundle embedded web app 2025-01-15 22:54:08 +00:00
Gabe Kangas 91a635a5ca chore(i18n): enable languages 2025-01-15 14:48:54 -08:00
088023cf15 Updated translations (#4117)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-15 14:41:18 -08:00
Gabe Kangas 0f48bc4904 chore(i18n): run workflow every hour + download translations for PR 2025-01-15 14:18:37 -08:00
Owncast e4aa69dc46 Bundle embedded web app 2025-01-15 22:04:41 +00:00
Gabe Kangas dc8ed52b58 fix(i18n): disable languages until the files exist 2025-01-15 13:59:35 -08:00
Gabe Kangas 648856dcd1 chore: update i18n workflow/config 2025-01-15 13:41:44 -08:00
renovate[bot]andGitHub a4ed2c14be chore(deps): update peter-evans/create-or-update-comment digest to fdb73c4 2025-01-15 05:33:32 +00:00
Owncast 8e2e05e48e Bundle embedded web app 2025-01-15 05:32:40 +00:00
Gabe Kangas d77b80a94a fix(storybook): fix another story not rendering with i18n 2025-01-14 21:27:21 -08:00
Owncast bd59d8ab40 Bundle embedded web app 2025-01-15 05:25:05 +00:00
Gabe Kangas 70282761d3 fix(storybook): fix storybook rendering with i18n library 2025-01-14 21:19:23 -08:00
Owncast e02f6dbc20 Bundle embedded web app 2025-01-15 04:59:24 +00:00
Gabe KangasGitHubOwncastgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Crowdin BottaintedcypherLe fractalSufyaan Khateebmahmed2000
cb387d88be Initial localization work (#3980)
* First pass at configuring localization

* Add CI job for translations

* Update CI job

* Update default value

* Update parser config

* Update defaults again

* try to fix the multiple parsing of a file

* Update crowdlin config

* Update configs

* New Crowdin translations by GitHub Action (#3448)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>

* Point to updated translated files

* Tooltip i18n

* Run translation job when web components are updated

* Commit updated translations

* Translations update (#3453)

* Update source file strings.json
Updated translations

* New translations strings.json (French)
Updated translations

* New translations strings.json (Spanish)
Updated translations

* New translations strings.json (German)
Updated translations

* New translations strings.json (English, United States)
Updated translations

* Commit updated translations

* New Crowdin translations by GitHub Action (#3452)

Co-authored-by: Owncast <owncast@owncast.online>

* chore(deps): update to next config to address build errors

* New Crowdin translations by GitHub Action (#3455)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>

* Translations update (#3456)

* New translations strings.json (Arabic)
Updated translations

* New translations strings.json (German)
Updated translations

* New translations strings.json (Greek)
Updated translations

* New translations strings.json (Irish)
Updated translations

* New translations strings.json (Italian)
Updated translations

* New translations strings.json (Japanese)
Updated translations

* New translations strings.json (Korean)
Updated translations

* New translations strings.json (Dutch)
Updated translations

* New translations strings.json (Norwegian)
Updated translations

* New translations strings.json (Punjabi)
Updated translations

* New translations strings.json (Russian)
Updated translations

* New translations strings.json (Swedish)
Updated translations

* New translations strings.json (Chinese Traditional)
Updated translations

* New translations strings.json (Vietnamese)
Updated translations

* New translations strings.json (Bengali)
Updated translations

* New translations strings.json (Thai)
Updated translations

* New translations strings.json (Croatian)
Updated translations

* New translations strings.json (Hindi)
Updated translations

* New translations strings.json (Malay)
Updated translations

* New Crowdin translations by GitHub Action (#3457)

* New translations strings.json (Arabic)
Updated translations

* New translations strings.json (German)
Updated translations

* New translations strings.json (Greek)
Updated translations

* New translations strings.json (Irish)
Updated translations

* New translations strings.json (Italian)
Updated translations

* New translations strings.json (Japanese)
Updated translations

* New translations strings.json (Korean)
Updated translations

* New translations strings.json (Dutch)
Updated translations

* New translations strings.json (Norwegian)
Updated translations

* New translations strings.json (Punjabi)
Updated translations

* New translations strings.json (Russian)
Updated translations

* New translations strings.json (Swedish)
Updated translations

* New translations strings.json (Chinese Traditional)
Updated translations

* New translations strings.json (Vietnamese)
Updated translations

* New translations strings.json (Bengali)
Updated translations

* New translations strings.json (Thai)
Updated translations

* New translations strings.json (Croatian)
Updated translations

* New translations strings.json (Hindi)
Updated translations

* New translations strings.json (Malay)
Updated translations

* New Crowdin translations by GitHub Action

---------

Co-authored-by: Gabe Kangas <gabek@real-ity.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>

* Commit updated API documentation

* Update translations job

* New Crowdin translations by GitHub Action (#3698)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>

* Update Crowdin configuration file

* Translations update (#3700)

* New translations strings.json (French)
Updated translations

* New translations strings.json (Italian)
Updated translations

* Translations update (#3699)

* New translations strings.json (French)
Updated translations

* New translations strings.json (Spanish)
Updated translations

* New translations strings.json (Italian)
Updated translations

* New translations strings.json (Japanese)
Updated translations

* New translations strings.json (Polish)
Updated translations

* New translations strings.json (Russian)
Updated translations

* New translations strings.json (Portuguese, Brazilian)
Updated translations

* Commit updated API documentation

---------

Co-authored-by: Owncast <owncast@owncast.online>

* New Crowdin translations by GitHub Action (#3701)

* New translations strings.json (French)
Updated translations

* New translations strings.json (Spanish)
Updated translations

* New translations strings.json (Italian)
Updated translations

* New translations strings.json (Japanese)
Updated translations

* New translations strings.json (Polish)
Updated translations

* New translations strings.json (Russian)
Updated translations

* New translations strings.json (Portuguese, Brazilian)
Updated translations

* New Crowdin translations by GitHub Action

---------

Co-authored-by: Gabe Kangas <gabek@real-ity.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>

* Draft: Mark strings for translation. (#3458)

* Mark strings for translation.

* Mark up strings for translation

* fix(web): fix linter warnings

---------

Co-authored-by: Le fractal <17422-fractal@users.noreply.framagit.org>
Co-authored-by: Gabe Kangas <gabek@real-ity.com>

* do not pull from cowdin via workflow

* Commit updated translations

* feat: add translations support to admin pages and components (#3977)

* feat: add translations support to admin pages and components

Added translations support admin main page and its components, help
page, handware-info page. Added translations support for LogTable,
NewsFeed and StreamHealthOverview components.

* update package.json

* fix rendering issue

* Commit updated API documentation

---------

Co-authored-by: Owncast <owncast@owncast.online>
Co-authored-by: Gabe Kangas <gabek@real-ity.com>

* Offline banner i18n formatting (#3997)

* Fix "Last live ago" string formatting with i18n interpolation

* Change some base translation jsons to use i18n interpolation

* Linting fix

* chore(js): ignore i18n pkgs in knip

* fix(test): fix browser ui test

* fix(js): remove unused var

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Owncast <owncast@owncast.online>
Co-authored-by: taintedcypher <119351153+taintedcypher@users.noreply.github.com>
Co-authored-by: Le fractal <17422-fractal@users.noreply.framagit.org>
Co-authored-by: Sufyaan Khateeb <81009832+SufyaanKhateeb@users.noreply.github.com>
Co-authored-by: mahmed2000 <mahmad2000@protonmail.com>
2025-01-14 20:54:21 -08:00
Gabe Kangas b45552ade0 fix(video): remove persistent HTTP connection. Hopefully fixes #4106 2025-01-14 08:37:27 -08:00
Gabe Kangas 2b42ff5ce4 Revert "fix(js): do not fire dangerouslySetInnerHTML if there is nothing to render. Closes #4075"
This reverts commit d91c42bafd9136805fd64018f0db517156bcb263.

Closes #4112
2025-01-14 08:33:03 -08:00
Owncast a9e4ad55e9 Bundle embedded web app 2025-01-13 12:36:22 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
5ccdcc7a9b chore(deps): lock file maintenance (#4109)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 12:30:54 +00:00
Owncast 271d9c3b87 Commit updated API documentation 2025-01-13 04:41:40 +00:00
Gabe Kangas 0d46f4a7d0 chore: bump version number to v0.2.1 2025-01-12 20:39:37 -08:00
8ce270b02f feat: enable Intel Quicksync with support for FFmpeg 4.x to 7.1 (#4028)
* feat: enable Intel Quicksync with support for FFmpeg 4.x to 7.1

* fix(go): fix linter warning

---------

Co-authored-by: Gabe Kangas <gabek@real-ity.com>
2025-01-12 20:38:26 -08:00
Martin WimpressandGitHub bb4a7479b5 fix: universal Intel and AMD VA-API accelerated pipeline for FFmpeg 4 - 7 (#4022) 2025-01-12 20:10:00 -08:00
211 changed files with 10821 additions and 5732 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
issues: write
steps:
- name: Add comment
uses: peter-evans/create-or-update-comment@853a4fc475ab347cfa392aa2ee451b4fe83e774e
uses: peter-evans/create-or-update-comment@2ef7be1cfae84b02f7b4e47ce45c1f93762d1d92
with:
issue-number: ${{ github.event.issue.number }}
body: |
+6 -1
View File
@@ -3,7 +3,12 @@ on:
push:
branches:
- develop
paths: ['web/stories/**', 'web/components/**', 'web/.storybook/**'] # Trigger the action only when files change in the folders defined here
paths: [
'web/stories/**',
'web/components/**',
'web/.storybook/**',
'web/i18n/**',
] # Trigger the action only when files change in the folders defined here
jobs:
build-and-deploy:
+62
View File
@@ -0,0 +1,62 @@
name: Translation job
on:
schedule:
# Run the workflow every hour
- cron: '0 * * * *'
push:
paths:
- 'web/i18n/en/translation.json'
- 'web/**/*.tsx'
- 'web/**/*.js'
- 'crowdin.yml'
- '.github/workflows/translations.yml'
- 'web/i18next-parser.config.mjs'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
generate-translations:
defaults:
run:
working-directory: ./web
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' }}
run: npm install
- name: Generate translation files
run: npm run translate
- name: Crowdin upload sources/download translations
uses: crowdin/github-action@v1
with:
upload_sources: true
download_translations: true
create_pull_request: true
pull_request_title: 'New Translations'
localization_branch_name: translations
pull_request_base_branch_name: 'develop'
commit_message: 'Updated translations'
config: crowdin.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
# - name: Commit changes
# uses: EndBug/add-and-commit@v9
# with:
# author_name: Owncast
# author_email: owncast@owncast.online
# message: 'Commit updated translations'
# add: 'web/i18n/**'
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1
View File
@@ -4,6 +4,7 @@
*.dll
*.so
*.dylib
.DS_Store
# Test binary, built with `go test -c`
*.test
+1 -1
View File
@@ -4,7 +4,7 @@ import "path/filepath"
const (
// StaticVersionNumber is the version of Owncast that is used when it's not overwritten via build-time settings.
StaticVersionNumber = "0.2.0" // Shown when you build from develop
StaticVersionNumber = "0.2.2" // Shown when you build from develop
// FfmpegSuggestedVersion is the version of ffmpeg we suggest.
FfmpegSuggestedVersion = "v4.1.5" // Requires the v
// DataDirectory is the directory we save data to.
+6 -3
View File
@@ -4,6 +4,7 @@ import (
"time"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/webserver/handlers/generated"
)
// Defaults will hold default configuration values.
@@ -25,7 +26,7 @@ type Defaults struct {
WebServerIP string
Name string
AdminPassword string
StreamKeys []models.StreamKey
StreamKeys []generated.StreamKey
StreamVariants []models.StreamOutputVariant
@@ -43,14 +44,16 @@ type Defaults struct {
// GetDefaults will return default configuration values.
func GetDefaults() Defaults {
defaultStreamKey := "abc123"
defaultStreamKeyComment := "Default stream key"
return Defaults{
Name: "New Owncast Server",
Summary: "This is a new live video streaming server powered by Owncast.",
ServerWelcomeMessage: "",
Logo: "logo.svg",
AdminPassword: "abc123",
StreamKeys: []models.StreamKey{
{Key: "abc123", Comment: "Default stream key"},
StreamKeys: []generated.StreamKey{
{Key: &defaultStreamKey, Comment: &defaultStreamKeyComment},
},
Tags: []string{
"owncast",
+7 -3
View File
@@ -8,6 +8,7 @@ import (
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/persistence/chatmessagerepository"
"github.com/owncast/owncast/persistence/configrepository"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
@@ -103,7 +104,8 @@ func SendSystemMessage(text string, ephemeral bool) error {
}
if !ephemeral {
saveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
chatMessageRepository := chatmessagerepository.Get()
chatMessageRepository.SaveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
}
return nil
@@ -131,7 +133,8 @@ func SendFediverseAction(eventType string, userAccountName string, image *string
return err
}
saveFederatedAction(message)
chatMessageRepository := chatmessagerepository.Get()
chatMessageRepository.SaveFederatedAction(message)
return nil
}
@@ -152,7 +155,8 @@ func SendSystemAction(text string, ephemeral bool) error {
}
if !ephemeral {
saveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
chatMessageRepository := chatmessagerepository.Get()
chatMessageRepository.SaveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
}
return nil
+3 -2
View File
@@ -9,6 +9,7 @@ import (
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/webhooks"
"github.com/owncast/owncast/persistence/chatmessagerepository"
"github.com/owncast/owncast/persistence/configrepository"
"github.com/owncast/owncast/persistence/userrepository"
"github.com/owncast/owncast/utils"
@@ -172,8 +173,8 @@ func (s *Server) userMessageSent(eventData chatClientEvent) {
// Send chat message sent webhook
webhooks.SendChatEvent(&event)
chatMessagesSentCounter.Inc()
SaveUserMessage(event)
chatMessageRepository := chatmessagerepository.Get()
chatMessageRepository.SaveUserMessage(event)
eventData.client.MessageCount++
}
+3 -1
View File
@@ -5,13 +5,15 @@ import (
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/webhooks"
"github.com/owncast/owncast/persistence/chatmessagerepository"
log "github.com/sirupsen/logrus"
)
// SetMessagesVisibility will set the visibility of multiple messages by ID.
func SetMessagesVisibility(messageIDs []string, visibility bool) error {
// Save new message visibility
if err := saveMessageVisibility(messageIDs, visibility); err != nil {
chatMessageRepository := chatmessagerepository.Get()
if err := chatMessageRepository.SetMessageVisibilityForMessageIDs(messageIDs, visibility); err != nil {
log.Errorln(err)
return err
}
-461
View File
@@ -1,25 +1,17 @@
package chat
import (
"context"
"database/sql"
"strings"
"time"
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/persistence/authrepository"
"github.com/owncast/owncast/persistence/tables"
log "github.com/sirupsen/logrus"
)
var _datastore *data.Datastore
const (
maxBacklogHours = 2 // Keep backlog max hours worth of messages
maxBacklogNumber = 50 // Return max number of messages in history request
)
func setupPersistence() {
@@ -37,456 +29,3 @@ func setupPersistence() {
}
}()
}
// SaveUserMessage will save a single chat event to the messages database.
func SaveUserMessage(event events.UserMessageEvent) {
saveEvent(event.ID, &event.User.ID, event.Body, event.Type, event.HiddenAt, event.Timestamp, nil, nil, nil, nil)
}
func saveFederatedAction(event events.FediverseEngagementEvent) {
saveEvent(event.ID, nil, event.Body, event.Type, nil, event.Timestamp, event.Image, &event.Link, &event.UserAccountName, nil)
}
// nolint: unparam
func saveEvent(id string, userID *string, body string, eventType string, hidden *time.Time, timestamp time.Time, image *string, link *string, title *string, subtitle *string) {
defer func() {
_historyCache = nil
}()
tx, err := _datastore.DB.Begin()
if err != nil {
log.Errorln("error saving", eventType, err)
return
}
defer tx.Rollback() // nolint
stmt, err := tx.Prepare("INSERT INTO messages(id, user_id, body, eventType, hidden_at, timestamp, image, link, title, subtitle) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
if err != nil {
log.Errorln("error saving", eventType, err)
return
}
defer stmt.Close()
if _, err = stmt.Exec(id, userID, body, eventType, hidden, timestamp, image, link, title, subtitle); err != nil {
log.Errorln("error saving", eventType, err)
return
}
if err = tx.Commit(); err != nil {
log.Errorln("error saving", eventType, err)
return
}
}
func makeUserMessageEventFromRowData(row rowData) events.UserMessageEvent {
scopes := ""
if row.userScopes != nil {
scopes = *row.userScopes
}
previousUsernames := []string{}
if row.previousUsernames != nil {
previousUsernames = strings.Split(*row.previousUsernames, ",")
}
displayName := ""
if row.userDisplayName != nil {
displayName = *row.userDisplayName
}
displayColor := 0
if row.userDisplayColor != nil {
displayColor = *row.userDisplayColor
}
createdAt := time.Time{}
if row.userCreatedAt != nil {
createdAt = *row.userCreatedAt
}
isBot := (row.userType != nil && *row.userType == "API")
scopeSlice := strings.Split(scopes, ",")
u := models.User{
ID: *row.userID,
DisplayName: displayName,
DisplayColor: displayColor,
CreatedAt: createdAt,
DisabledAt: row.userDisabledAt,
NameChangedAt: row.userNameChangedAt,
PreviousNames: previousUsernames,
AuthenticatedAt: row.userAuthenticatedAt,
Authenticated: row.userAuthenticatedAt != nil,
Scopes: scopeSlice,
IsBot: isBot,
}
message := events.UserMessageEvent{
Event: events.Event{
Type: row.eventType,
ID: row.id,
Timestamp: row.timestamp,
},
UserEvent: events.UserEvent{
User: &u,
HiddenAt: row.hiddenAt,
},
MessageEvent: events.MessageEvent{
Body: row.body,
RawBody: row.body,
},
}
return message
}
func makeSystemMessageChatEventFromRowData(row rowData) events.SystemMessageEvent {
message := events.SystemMessageEvent{
Event: events.Event{
Type: row.eventType,
ID: row.id,
Timestamp: row.timestamp,
},
MessageEvent: events.MessageEvent{
Body: row.body,
RawBody: row.body,
},
}
return message
}
func makeActionMessageChatEventFromRowData(row rowData) events.ActionEvent {
message := events.ActionEvent{
Event: events.Event{
Type: row.eventType,
ID: row.id,
Timestamp: row.timestamp,
},
MessageEvent: events.MessageEvent{
Body: row.body,
RawBody: row.body,
},
}
return message
}
func makeFederatedActionChatEventFromRowData(row rowData) events.FediverseEngagementEvent {
message := events.FediverseEngagementEvent{
Event: events.Event{
Type: row.eventType,
ID: row.id,
Timestamp: row.timestamp,
},
MessageEvent: events.MessageEvent{
Body: row.body,
RawBody: row.body,
},
Image: row.image,
Link: *row.link,
UserAccountName: *row.title,
}
return message
}
type rowData struct {
timestamp time.Time
image *string
previousUsernames *string
userDisplayName *string
userDisplayColor *int
userID *string
title *string
subtitle *string
link *string
userType *string
userScopes *string
hiddenAt *time.Time
userCreatedAt *time.Time
userDisabledAt *time.Time
userAuthenticatedAt *time.Time
userNameChangedAt *time.Time
body string
eventType models.EventType
id string
}
func getChat(rows *sql.Rows) ([]interface{}, error) {
history := make([]interface{}, 0)
for rows.Next() {
row := rowData{}
// Convert a database row into a chat event
if err := rows.Scan(
&row.id,
&row.userID,
&row.body,
&row.title,
&row.subtitle,
&row.image,
&row.link,
&row.eventType,
&row.hiddenAt,
&row.timestamp,
&row.userDisplayName,
&row.userDisplayColor,
&row.userCreatedAt,
&row.userDisabledAt,
&row.previousUsernames,
&row.userNameChangedAt,
&row.userAuthenticatedAt,
&row.userScopes,
&row.userType,
); err != nil {
return nil, err
}
var message interface{}
switch row.eventType {
case events.MessageSent:
message = makeUserMessageEventFromRowData(row)
case events.SystemMessageSent:
message = makeSystemMessageChatEventFromRowData(row)
case events.ChatActionSent:
message = makeActionMessageChatEventFromRowData(row)
case events.FediverseEngagementFollow:
message = makeFederatedActionChatEventFromRowData(row)
case events.FediverseEngagementLike:
message = makeFederatedActionChatEventFromRowData(row)
case events.FediverseEngagementRepost:
message = makeFederatedActionChatEventFromRowData(row)
}
history = append(history, message)
}
return history, nil
}
var _historyCache *[]interface{}
// GetChatModerationHistory will return all the chat messages suitable for moderation purposes.
func GetChatModerationHistory() []interface{} {
if _historyCache != nil {
return *_historyCache
}
tx, err := _datastore.DB.Begin()
if err != nil {
log.Errorln("error fetching chat moderation history", err)
return nil
}
defer tx.Rollback() // nolint
// Get all messages regardless of visibility
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id ORDER BY timestamp DESC"
stmt, err := tx.Prepare(query)
if err != nil {
log.Errorln("error fetching chat moderation history", err)
return nil
}
rows, err := stmt.Query()
if err != nil {
log.Errorln("error fetching chat moderation history", err)
return nil
}
defer stmt.Close()
defer rows.Close()
result, err := getChat(rows)
if err != nil {
log.Errorln(err)
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
return nil
}
_historyCache = &result
if err = tx.Commit(); err != nil {
log.Errorln("error fetching chat moderation history", err)
return nil
}
return result
}
// GetChatHistory will return all the chat messages suitable for returning as user-facing chat history.
func GetChatHistory() []interface{} {
tx, err := _datastore.DB.Begin()
if err != nil {
log.Errorln("error fetching chat history", err)
return nil
}
defer tx.Rollback() // nolint
// Get all visible messages
query := "SELECT messages.id, messages.user_id, messages.body, messages.title, messages.subtitle, messages.image, messages.link, messages.eventType, messages.hidden_at, messages.timestamp, users.display_name, users.display_color, users.created_at, users.disabled_at, users.previous_names, users.namechanged_at, users.authenticated_at, users.scopes, users.type FROM users JOIN messages ON users.id = messages.user_id WHERE hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT ?"
stmt, err := tx.Prepare(query)
if err != nil {
log.Errorln("error fetching chat history", err)
return nil
}
rows, err := stmt.Query(maxBacklogNumber)
if err != nil {
log.Errorln("error fetching chat history", err)
return nil
}
defer stmt.Close()
defer rows.Close()
m, err := getChat(rows)
if err != nil {
log.Errorln(err)
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
return nil
}
if err = tx.Commit(); err != nil {
log.Errorln("error fetching chat history", err)
return nil
}
// Invert order of messages
for i, j := 0, len(m)-1; i < j; i, j = i+1, j-1 {
m[i], m[j] = m[j], m[i]
}
return m
}
// GetMessagesFromUser returns chat messages that were sent by a specific user.
func GetMessagesFromUser(userID string) ([]events.UserMessageEvent, error) {
query, err := _datastore.GetQueries().GetMessagesFromUser(context.Background(), sql.NullString{String: userID, Valid: true})
if err != nil {
return nil, err
}
results := make([]events.UserMessageEvent, len(query))
for i, row := range query {
results[i] = events.UserMessageEvent{
Event: events.Event{
Timestamp: row.Timestamp.Time,
ID: row.ID,
},
MessageEvent: events.MessageEvent{
Body: row.Body.String,
},
}
}
return results, nil
}
// SetMessageVisibilityForUserID will bulk change the visibility of messages for a user
// and then send out visibility changed events to chat clients.
func SetMessageVisibilityForUserID(userID string, visible bool) error {
defer func() {
_historyCache = nil
}()
tx, err := _datastore.DB.Begin()
if err != nil {
log.Errorln("error while setting message visibility", err)
return nil
}
defer tx.Rollback() // nolint
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id WHERE user_id IS ?"
stmt, err := tx.Prepare(query)
if err != nil {
log.Errorln("error while setting message visibility", err)
return nil
}
rows, err := stmt.Query(userID)
if err != nil {
log.Errorln("error while setting message visibility", err)
return nil
}
defer stmt.Close()
defer rows.Close()
// Get a list of IDs to send to the connected clients to hide
ids := make([]string, 0)
messages, err := getChat(rows)
if err != nil {
log.Errorln(err)
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
return nil
}
if len(messages) == 0 {
return nil
}
for _, message := range messages {
ids = append(ids, message.(events.UserMessageEvent).ID)
}
if err = tx.Commit(); err != nil {
log.Errorln("error while setting message visibility ", err)
return nil
}
// Tell the clients to hide/show these messages.
return SetMessagesVisibility(ids, visible)
}
func saveMessageVisibility(messageIDs []string, visible bool) error {
defer func() {
_historyCache = nil
}()
_datastore.DbLock.Lock()
defer _datastore.DbLock.Unlock()
tx, err := _datastore.DB.Begin()
if err != nil {
return err
}
// nolint:gosec
stmt, err := tx.Prepare("UPDATE messages SET hidden_at=? WHERE id IN (?" + strings.Repeat(",?", len(messageIDs)-1) + ")")
if err != nil {
return err
}
defer stmt.Close()
var hiddenAt *time.Time
if !visible {
now := time.Now()
hiddenAt = &now
} else {
hiddenAt = nil
}
args := make([]interface{}, len(messageIDs)+1)
args[0] = hiddenAt
for i, id := range messageIDs {
args[i+1] = id
}
if _, err = stmt.Exec(args...); err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
return nil
}
+2 -1
View File
@@ -75,7 +75,8 @@ func SetupPersistence(file string) error {
_, _ = db.Exec("pragma temp_store = memory")
_, _ = db.Exec("pragma wal_checkpoint(full)")
createWebhooksTable()
tables.CreateConfigTable(db)
tables.CreateWebhooksTable(db)
tables.CreateUsersTable(db)
tables.CreateAccessTokenTable(db)
+1 -9
View File
@@ -107,19 +107,11 @@ func (ds *Datastore) Save(e models.ConfigEntry) error {
return nil
}
// Setup will create the datastore table and perform initial initialization.
// Setup will perform initial initialization.
func (ds *Datastore) Setup() {
ds.cache = make(map[string][]byte)
ds.DB = GetDatabase()
ds.DbLock = &sync.Mutex{}
createTableSQL := `CREATE TABLE IF NOT EXISTS datastore (
"key" string NOT NULL PRIMARY KEY,
"value" BLOB,
"timestamp" DATE DEFAULT CURRENT_TIMESTAMP NOT NULL
);`
ds.MustExec(createTableSQL)
}
// Reset will delete all config entries in the datastore and start over.
-18
View File
@@ -1,18 +0,0 @@
package data
// GetMessagesCount will return the number of messages in the database.
func GetMessagesCount() int64 {
query := `SELECT COUNT(*) FROM messages`
rows, err := _db.Query(query)
if err != nil || rows.Err() != nil {
return 0
}
defer rows.Close()
var count int64
for rows.Next() {
if err := rows.Scan(&count); err != nil {
return 0
}
}
return count
}
+3 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/persistence/configrepository"
"github.com/owncast/owncast/webserver/handlers/generated"
)
var _hasInboundRTMPConnection = false
@@ -87,11 +88,11 @@ func HandleConn(c *rtmp.Conn, nc net.Conn) {
// If a stream key override was specified then use that instead.
if config.TemporaryStreamKey != "" {
validStreamingKeys = []models.StreamKey{{Key: config.TemporaryStreamKey}}
validStreamingKeys = []generated.StreamKey{{Key: &config.TemporaryStreamKey}}
}
for _, key := range validStreamingKeys {
if secretMatch(key.Key, c.URL.Path) {
if key.Key != nil && secretMatch(*key.Key, c.URL.Path) {
accessGranted = true
break
}
+18 -12
View File
@@ -27,6 +27,7 @@ var supportedCodecs = map[string]string{
(&Libx264Codec{}).Name(): "libx264",
(&OmxCodec{}).Name(): "omx",
(&VaapiCodec{}).Name(): "vaapi",
(&QuicksyncCodec{}).Name(): "qsv",
(&NvencCodec{}).Name(): "NVIDIA nvenc",
(&VideoToolboxCodec{}).Name(): "videotoolbox",
}
@@ -191,7 +192,7 @@ func (c *VaapiCodec) GlobalFlags() string {
// PixelFormat is the pixel format required for this codec.
func (c *VaapiCodec) PixelFormat() string {
return "vaapi_vld"
return "vaapi"
}
// Scaler is the scaler used for resizing the video in the transcoder.
@@ -201,7 +202,7 @@ func (c *VaapiCodec) Scaler() string {
// ExtraFilters are the extra filters required for this codec in the transcoder.
func (c *VaapiCodec) ExtraFilters() string {
return ""
return "hwupload=extra_hw_frames=64,format=vaapi"
}
// ExtraArguments are the extra arguments used with this codec in the transcoder.
@@ -317,17 +318,22 @@ func (c *QuicksyncCodec) DisplayName() string {
// GlobalFlags are the global flags used with this codec in the transcoder.
func (c *QuicksyncCodec) GlobalFlags() string {
return ""
flags := []string{
"-init_hw_device", "qsv=hw",
"-filter_hw_device", "hw",
}
return strings.Join(flags, " ")
}
// PixelFormat is the pixel format required for this codec.
func (c *QuicksyncCodec) PixelFormat() string {
return "nv12"
return "qsv"
}
// Scaler is the scaler used for resizing the video in the transcoder.
func (c *QuicksyncCodec) Scaler() string {
return ""
return "scale_qsv"
}
// ExtraArguments are the extra arguments used with this codec in the transcoder.
@@ -337,7 +343,7 @@ func (c *QuicksyncCodec) ExtraArguments() string {
// ExtraFilters are the extra filters required for this codec in the transcoder.
func (c *QuicksyncCodec) ExtraFilters() string {
return ""
return "hwupload=extra_hw_frames=64,format=qsv"
}
// VariantFlags returns a string representing a single variant processed by this codec.
@@ -348,16 +354,16 @@ func (c *QuicksyncCodec) VariantFlags(v *HLSVariant) string {
// GetPresetForLevel returns the string preset for this codec given an integer level.
func (c *QuicksyncCodec) GetPresetForLevel(l int) string {
presetMapping := map[int]string{
0: "ultrafast",
1: "superfast",
2: "veryfast",
3: "faster",
4: "fast",
0: "veryfast",
1: "fast",
2: "medium",
3: "slow",
4: "veryslow",
}
preset, ok := presetMapping[l]
if !ok {
defaultPreset := presetMapping[1]
defaultPreset := presetMapping[2]
log.Errorf("Invalid level for quicksync preset %d, defaulting to %s", l, defaultPreset)
return defaultPreset
}
-1
View File
@@ -230,7 +230,6 @@ func (t *Transcoder) getString() string {
"-max_muxing_queue_size", "400", // Workaround for Too many packets error: https://trac.ffmpeg.org/ticket/6375?cversion=0
"-method PUT", // HLS results sent back to us will be over PUTs
"-http_persistent", "1", // Ensures persistent HTTP connections
localListenerAddress + "/%v/stream.m3u8", // Send HLS playlists back to us over HTTP
}
+1 -1
View File
@@ -42,7 +42,7 @@ func TestFFmpegNvencCommand(t *testing.T) {
cmd := transcoder.getString()
expectedLogPath := filepath.Join("data", "logs", "transcoder.log")
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel cuda -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_nvenc -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -tune:v:0 ll -map a:0? -c:a:0 copy -preset p3 -map v:0 -c:v:1 h264_nvenc -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -tune:v:1 ll -map a:0? -c:a:1 copy -preset p5 -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset p1 -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdoieGg-%d.ts -max_muxing_queue_size 400 -method PUT -http_persistent 1 http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel cuda -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_nvenc -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -tune:v:0 ll -map a:0? -c:a:0 copy -preset p3 -map v:0 -c:v:1 h264_nvenc -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -tune:v:1 ll -map a:0? -c:a:1 copy -preset p5 -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset p1 -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdoieGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+1 -1
View File
@@ -42,7 +42,7 @@ func TestFFmpegOmxCommand(t *testing.T) {
cmd := transcoder.getString()
expectedLogPath := filepath.Join("data", "logs", "transcoder.log")
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_omx -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_omx -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -tune zerolatency -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.ts -max_muxing_queue_size 400 -method PUT -http_persistent 1 http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_omx -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_omx -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -tune zerolatency -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+50
View File
@@ -0,0 +1,50 @@
package transcoder
import (
"path/filepath"
"testing"
"github.com/owncast/owncast/models"
)
func TestFFmpegQuicksyncCommand(t *testing.T) {
latencyLevel := models.GetLatencyLevel(2)
codec := QuicksyncCodec{}
transcoder := new(Transcoder)
transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg")
transcoder.SetInput("fakecontent.flv")
transcoder.SetOutputPath("fakeOutput")
transcoder.SetIdentifier("jdofFGg")
transcoder.SetInternalHTTPPort("8123")
transcoder.SetCodec(codec.Name())
transcoder.currentLatencyLevel = latencyLevel
variant := HLSVariant{}
variant.videoBitrate = 1200
variant.isAudioPassthrough = true
variant.SetVideoFramerate(30)
variant.SetCPUUsageLevel(2)
transcoder.AddVariant(variant)
variant2 := HLSVariant{}
variant2.videoBitrate = 3500
variant2.isAudioPassthrough = true
variant2.SetVideoFramerate(24)
variant2.SetCPUUsageLevel(4)
transcoder.AddVariant(variant2)
variant3 := HLSVariant{}
variant3.isAudioPassthrough = true
variant3.isVideoPassthrough = true
transcoder.AddVariant(variant3)
cmd := transcoder.getString()
expectedLogPath := filepath.Join("data", "logs", "transcoder.log")
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -init_hw_device qsv=hw -filter_hw_device hw -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_qsv -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 "hwupload=extra_hw_frames=64,format=qsv" -preset medium -map v:0 -c:v:1 h264_qsv -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 "hwupload=extra_hw_frames=64,format=qsv" -preset veryslow -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset veryfast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt qsv -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
}
}
+1 -1
View File
@@ -42,7 +42,7 @@ func TestFFmpegVaapiCommand(t *testing.T) {
cmd := transcoder.getString()
expectedLogPath := filepath.Join("data", "logs", "transcoder.log")
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_vaapi -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_vaapi -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt vaapi_vld -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT -http_persistent 1 http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_vaapi -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 "hwupload=extra_hw_frames=64,format=vaapi" -preset veryfast -map v:0 -c:v:1 h264_vaapi -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 "hwupload=extra_hw_frames=64,format=vaapi" -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt vaapi -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
@@ -42,7 +42,7 @@ func TestFFmpegVideoToolboxCommand(t *testing.T) {
cmd := transcoder.getString()
expectedLogPath := filepath.Join("data", "logs", "transcoder.log")
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_videotoolbox -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -realtime true -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_videotoolbox -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt nv12 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.ts -max_muxing_queue_size 400 -method PUT -http_persistent 1 http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 h264_videotoolbox -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -realtime true -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_videotoolbox -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt nv12 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+1 -1
View File
@@ -42,7 +42,7 @@ func TestFFmpegx264Command(t *testing.T) {
cmd := transcoder.getString()
expectedLogPath := filepath.Join("data", "logs", "transcoder.log")
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 libx264 -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -x264-params:v:0 "scenecut=0:open_gop=0" -bufsize:v:0 1088k -profile:v:0 high -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 libx264 -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -x264-params:v:1 "scenecut=0:open_gop=0" -bufsize:v:1 3572k -profile:v:1 high -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -tune zerolatency -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT -http_persistent 1 http://127.0.0.1:8123/%v/stream.m3u8`
expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -flags +cgop -i fakecontent.flv -map v:0 -c:v:0 libx264 -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -x264-params:v:0 "scenecut=0:open_gop=0" -bufsize:v:0 1088k -profile:v:0 high -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 libx264 -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -x264-params:v:1 "scenecut=0:open_gop=0" -bufsize:v:1 3572k -profile:v:1 high -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -tune zerolatency -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected)
+2
View File
@@ -59,6 +59,8 @@ var ignoredErrors = []string{
"Non-monotonous DTS in output",
"frames duplicated",
"To ignore this",
"Driver does not support some wanted packed headers (wanted 0xd, found 0x1)",
"Failed to allocate a vaapi/nv12 frame from a fixed pool of hardware frames.",
}
func handleTranscoderMessage(message string) {
+3 -2
View File
@@ -4,8 +4,8 @@ import (
"sync"
"time"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/persistence/webhookrepository"
)
// WebhookEvent represents an event sent as a webhook.
@@ -31,7 +31,8 @@ func SendEventToWebhooks(payload WebhookEvent) {
}
func sendEventToWebhooks(payload WebhookEvent, wg *sync.WaitGroup) {
webhooks := data.GetWebhooksForEvent(payload.Type)
webhooksRepo := webhookrepository.Get()
webhooks := webhooksRepo.GetWebhooksForEvent(payload.Type)
for _, webhook := range webhooks {
// Use wg to track the number of notifications to be sent.
+26 -13
View File
@@ -15,6 +15,7 @@ import (
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/persistence/webhookrepository"
jsonpatch "gopkg.in/evanphx/json-patch.v5"
)
@@ -62,12 +63,14 @@ func TestPublicSend(t *testing.T) {
}))
defer svr.Close()
hook, err := data.InsertWebhook(svr.URL, []models.EventType{models.MessageSent})
webhooksRepo := webhookrepository.Get()
hook, err := webhooksRepo.InsertWebhook(svr.URL, []models.EventType{models.MessageSent})
if err != nil {
t.Fatal(err)
}
defer func() {
if err := data.DeleteWebhook(hook); err != nil {
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
t.Error(err)
}
}()
@@ -107,13 +110,15 @@ func TestRouting(t *testing.T) {
}))
defer svr.Close()
webhooksRepo := webhookrepository.Get()
for _, eventType := range eventTypes {
hook, err := data.InsertWebhook(svr.URL+"/"+eventType, []models.EventType{eventType})
hook, err := webhooksRepo.InsertWebhook(svr.URL+"/"+eventType, []models.EventType{eventType})
if err != nil {
t.Fatal(err)
}
defer func() {
if err := data.DeleteWebhook(hook); err != nil {
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
t.Error(err)
}
}()
@@ -148,13 +153,15 @@ func TestMultiple(t *testing.T) {
}))
defer svr.Close()
webhooksRepo := webhookrepository.Get()
for i := 0; i < times; i++ {
hook, err := data.InsertWebhook(fmt.Sprintf("%v/%v", svr.URL, i), []models.EventType{models.MessageSent})
hook, err := webhooksRepo.InsertWebhook(fmt.Sprintf("%v/%v", svr.URL, i), []models.EventType{models.MessageSent})
if err != nil {
t.Fatal(err)
}
defer func() {
if err := data.DeleteWebhook(hook); err != nil {
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
t.Error(err)
}
}()
@@ -186,14 +193,16 @@ func TestTimestamps(t *testing.T) {
}))
defer svr.Close()
webhooksRepo := webhookrepository.Get()
for i, eventType := range eventTypes {
hook, err := data.InsertWebhook(svr.URL+"/"+eventType, []models.EventType{eventType})
hook, err := webhooksRepo.InsertWebhook(svr.URL+"/"+eventType, []models.EventType{eventType})
if err != nil {
t.Fatal(err)
}
handlerIds[i] = hook
defer func() {
if err := data.DeleteWebhook(hook); err != nil {
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
t.Error(err)
}
}()
@@ -209,7 +218,7 @@ func TestTimestamps(t *testing.T) {
wg.Wait()
hooks, err := data.GetWebhooks()
hooks, err := webhooksRepo.GetWebhooks()
if err != nil {
t.Fatal(err)
}
@@ -285,12 +294,14 @@ func TestParallel(t *testing.T) {
}))
defer svr.Close()
hook, err := data.InsertWebhook(svr.URL, []models.EventType{models.MessageSent})
webhooksRepo := webhookrepository.Get()
hook, err := webhooksRepo.InsertWebhook(svr.URL, []models.EventType{models.MessageSent})
if err != nil {
t.Fatal(err)
}
defer func() {
if err := data.DeleteWebhook(hook); err != nil {
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
t.Error(err)
}
}()
@@ -320,13 +331,15 @@ func checkPayload(t *testing.T, eventType models.EventType, send func(), expecte
}))
defer svr.Close()
webhooksRepo := webhookrepository.Get()
// Subscribe to the webhook.
hook, err := data.InsertWebhook(svr.URL, []models.EventType{eventType})
hook, err := webhooksRepo.InsertWebhook(svr.URL, []models.EventType{eventType})
if err != nil {
t.Fatal(err)
}
defer func() {
if err := data.DeleteWebhook(hook); err != nil {
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
t.Error(err)
}
}()
+3 -2
View File
@@ -9,8 +9,8 @@ import (
log "github.com/sirupsen/logrus"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/persistence/webhookrepository"
)
// webhookWorkerPoolSize defines the number of concurrent HTTP webhook requests.
@@ -87,7 +87,8 @@ func sendWebhook(job Job) error {
defer resp.Body.Close()
if err := data.SetWebhookAsUsed(job.webhook); err != nil {
webhooksRepo := webhookrepository.Get()
if err := webhooksRepo.SetWebhookAsUsed(job.webhook); err != nil {
log.Warnln(err)
}
+11
View File
@@ -0,0 +1,11 @@
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_PERSONAL_TOKEN
pull_request_title: Translations update
pull_request_labels:
- crowdin
- i18n
- translation
commit_message: 'Updated translations for %language%'
files:
- source: /web/i18n/en/translation.json
translation: /web/i18n/%two_letters_code%/translation.json
+4533 -4533
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -9,7 +9,7 @@ require (
github.com/SherClockHolmes/webpush-go v1.4.0
github.com/TwiN/go-away v1.6.14
github.com/andybalholm/cascadia v1.3.3
github.com/aws/aws-sdk-go v1.55.5
github.com/aws/aws-sdk-go v1.55.6
github.com/go-chi/chi/v5 v5.2.0
github.com/go-fed/activity v1.0.1-0.20220119073622-b14b50eecad0
github.com/go-fed/httpsig v1.1.0
+2
View File
@@ -19,6 +19,8 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7D
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+3 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/nakabonne/tstorage"
"github.com/owncast/owncast/core"
"github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/persistence/chatmessagerepository"
"github.com/owncast/owncast/persistence/userrepository"
log "github.com/sirupsen/logrus"
)
@@ -56,7 +56,8 @@ func collectChatClientCount() {
activeChatClientCount.Set(float64(count))
// Total message count
cmc := data.GetMessagesCount()
chatMessageRepository := chatmessagerepository.Get()
cmc := chatMessageRepository.GetMessagesCount()
// Insert message count into Prometheus collector.
currentChatMessageCount.Set(float64(cmc))
-7
View File
@@ -1,7 +0,0 @@
package models
// StreamKey represents a single stream key.
type StreamKey struct {
Key string `json:"key"`
Comment string `json:"comment"`
}
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
version: 0.2.0
version: 0.2.2
title: Owncast APIs
description: |-
Internal
+6
View File
@@ -0,0 +1,6 @@
{
"name": "owncast",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
@@ -0,0 +1,523 @@
package chatmessagerepository
import (
"context"
"database/sql"
"errors"
"strings"
"time"
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
log "github.com/sirupsen/logrus"
)
const maxBacklogNumber = 50 // Return max number of messages in history request
type ChatMessageRepository interface {
SaveUserMessage(event events.UserMessageEvent)
SaveFederatedAction(event events.FediverseEngagementEvent)
SaveEvent(id string, userID *string, body string, eventType string, hidden *time.Time, timestamp time.Time, image *string, link *string, title *string, subtitle *string)
GetChatModerationHistory() []interface{}
GetChatHistory() []interface{}
GetMessagesFromUser(userID string) ([]events.UserMessageEvent, error)
GetMessageIdsForUserID(userID string) ([]string, error)
SetMessageVisibilityForMessageIDs(messageIDs []string, visible bool) error
GetMessagesCount() int64
}
type SqlChatMessageRepository struct {
datastore *data.Datastore
}
// NOTE: This is temporary during the transition period.
var temporaryGlobalInstance ChatMessageRepository
// Get will return the user repository.
func Get() ChatMessageRepository {
if temporaryGlobalInstance == nil {
i := New(data.GetDatastore())
temporaryGlobalInstance = i
}
return temporaryGlobalInstance
}
// New will create a new instance of the UserRepository.
func New(datastore *data.Datastore) ChatMessageRepository {
r := SqlChatMessageRepository{
datastore: datastore,
}
return &r
}
// SaveUserMessage will save a single chat event to the messages database.
func (r *SqlChatMessageRepository) SaveUserMessage(event events.UserMessageEvent) {
r.SaveEvent(event.ID, &event.User.ID, event.Body, event.Type, event.HiddenAt, event.Timestamp, nil, nil, nil, nil)
}
func (r *SqlChatMessageRepository) SaveFederatedAction(event events.FediverseEngagementEvent) {
r.SaveEvent(event.ID, nil, event.Body, event.Type, nil, event.Timestamp, event.Image, &event.Link, &event.UserAccountName, nil)
}
// nolint: unparam
func (r *SqlChatMessageRepository) SaveEvent(id string, userID *string, body string, eventType string, hidden *time.Time, timestamp time.Time, image *string, link *string, title *string, subtitle *string) {
defer func() {
_historyCache = nil
}()
tx, err := r.datastore.DB.Begin()
if err != nil {
log.Errorln("error saving", eventType, err)
return
}
defer tx.Rollback() // nolint
stmt, err := tx.Prepare("INSERT INTO messages(id, user_id, body, eventType, hidden_at, timestamp, image, link, title, subtitle) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
if err != nil {
log.Errorln("error saving", eventType, err)
return
}
defer stmt.Close()
if _, err = stmt.Exec(id, userID, body, eventType, hidden, timestamp, image, link, title, subtitle); err != nil {
log.Errorln("error saving", eventType, err)
return
}
if err = tx.Commit(); err != nil {
log.Errorln("error saving", eventType, err)
return
}
}
func makeUserMessageEventFromRowData(row rowData) events.UserMessageEvent {
scopes := ""
if row.userScopes != nil {
scopes = *row.userScopes
}
previousUsernames := []string{}
if row.previousUsernames != nil {
previousUsernames = strings.Split(*row.previousUsernames, ",")
}
displayName := ""
if row.userDisplayName != nil {
displayName = *row.userDisplayName
}
displayColor := 0
if row.userDisplayColor != nil {
displayColor = *row.userDisplayColor
}
createdAt := time.Time{}
if row.userCreatedAt != nil {
createdAt = *row.userCreatedAt
}
isBot := (row.userType != nil && *row.userType == "API")
scopeSlice := strings.Split(scopes, ",")
u := models.User{
ID: *row.userID,
DisplayName: displayName,
DisplayColor: displayColor,
CreatedAt: createdAt,
DisabledAt: row.userDisabledAt,
NameChangedAt: row.userNameChangedAt,
PreviousNames: previousUsernames,
AuthenticatedAt: row.userAuthenticatedAt,
Authenticated: row.userAuthenticatedAt != nil,
Scopes: scopeSlice,
IsBot: isBot,
}
message := events.UserMessageEvent{
Event: events.Event{
Type: row.eventType,
ID: row.id,
Timestamp: row.timestamp,
},
UserEvent: events.UserEvent{
User: &u,
HiddenAt: row.hiddenAt,
},
MessageEvent: events.MessageEvent{
Body: row.body,
RawBody: row.body,
},
}
return message
}
func makeSystemMessageChatEventFromRowData(row rowData) events.SystemMessageEvent {
message := events.SystemMessageEvent{
Event: events.Event{
Type: row.eventType,
ID: row.id,
Timestamp: row.timestamp,
},
MessageEvent: events.MessageEvent{
Body: row.body,
RawBody: row.body,
},
}
return message
}
func makeActionMessageChatEventFromRowData(row rowData) events.ActionEvent {
message := events.ActionEvent{
Event: events.Event{
Type: row.eventType,
ID: row.id,
Timestamp: row.timestamp,
},
MessageEvent: events.MessageEvent{
Body: row.body,
RawBody: row.body,
},
}
return message
}
func makeFederatedActionChatEventFromRowData(row rowData) events.FediverseEngagementEvent {
message := events.FediverseEngagementEvent{
Event: events.Event{
Type: row.eventType,
ID: row.id,
Timestamp: row.timestamp,
},
MessageEvent: events.MessageEvent{
Body: row.body,
RawBody: row.body,
},
Image: row.image,
Link: *row.link,
UserAccountName: *row.title,
}
return message
}
type rowData struct {
timestamp time.Time
image *string
previousUsernames *string
userDisplayName *string
userDisplayColor *int
userID *string
title *string
subtitle *string
link *string
userType *string
userScopes *string
hiddenAt *time.Time
userCreatedAt *time.Time
userDisabledAt *time.Time
userAuthenticatedAt *time.Time
userNameChangedAt *time.Time
body string
eventType models.EventType
id string
}
func getChat(rows *sql.Rows) ([]interface{}, error) {
history := make([]interface{}, 0)
for rows.Next() {
row := rowData{}
// Convert a database row into a chat event
if err := rows.Scan(
&row.id,
&row.userID,
&row.body,
&row.title,
&row.subtitle,
&row.image,
&row.link,
&row.eventType,
&row.hiddenAt,
&row.timestamp,
&row.userDisplayName,
&row.userDisplayColor,
&row.userCreatedAt,
&row.userDisabledAt,
&row.previousUsernames,
&row.userNameChangedAt,
&row.userAuthenticatedAt,
&row.userScopes,
&row.userType,
); err != nil {
return nil, err
}
var message interface{}
switch row.eventType {
case events.MessageSent:
message = makeUserMessageEventFromRowData(row)
case events.SystemMessageSent:
message = makeSystemMessageChatEventFromRowData(row)
case events.ChatActionSent:
message = makeActionMessageChatEventFromRowData(row)
case events.FediverseEngagementFollow:
message = makeFederatedActionChatEventFromRowData(row)
case events.FediverseEngagementLike:
message = makeFederatedActionChatEventFromRowData(row)
case events.FediverseEngagementRepost:
message = makeFederatedActionChatEventFromRowData(row)
}
history = append(history, message)
}
return history, nil
}
var _historyCache *[]interface{}
// GetChatModerationHistory will return all the chat messages suitable for moderation purposes.
func (r *SqlChatMessageRepository) GetChatModerationHistory() []interface{} {
if _historyCache != nil {
return *_historyCache
}
tx, err := r.datastore.DB.Begin()
if err != nil {
log.Errorln("error fetching chat moderation history", err)
return nil
}
defer tx.Rollback() // nolint
// Get all messages regardless of visibility
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id ORDER BY timestamp DESC"
stmt, err := tx.Prepare(query)
if err != nil {
log.Errorln("error fetching chat moderation history", err)
return nil
}
rows, err := stmt.Query()
if err != nil {
log.Errorln("error fetching chat moderation history", err)
return nil
}
defer stmt.Close()
defer rows.Close()
result, err := getChat(rows)
if err != nil {
log.Errorln(err)
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
return nil
}
_historyCache = &result
if err = tx.Commit(); err != nil {
log.Errorln("error fetching chat moderation history", err)
return nil
}
return result
}
// GetChatHistory will return all the chat messages suitable for returning as user-facing chat history.
func (r *SqlChatMessageRepository) GetChatHistory() []interface{} {
tx, err := r.datastore.DB.Begin()
if err != nil {
log.Errorln("error fetching chat history", err)
return nil
}
defer tx.Rollback() // nolint
// Get all visible messages
query := "SELECT messages.id, messages.user_id, messages.body, messages.title, messages.subtitle, messages.image, messages.link, messages.eventType, messages.hidden_at, messages.timestamp, users.display_name, users.display_color, users.created_at, users.disabled_at, users.previous_names, users.namechanged_at, users.authenticated_at, users.scopes, users.type FROM users JOIN messages ON users.id = messages.user_id WHERE hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT ?"
stmt, err := tx.Prepare(query)
if err != nil {
log.Errorln("error fetching chat history", err)
return nil
}
rows, err := stmt.Query(maxBacklogNumber)
if err != nil {
log.Errorln("error fetching chat history", err)
return nil
}
defer stmt.Close()
defer rows.Close()
m, err := getChat(rows)
if err != nil {
log.Errorln(err)
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
return nil
}
if err = tx.Commit(); err != nil {
log.Errorln("error fetching chat history", err)
return nil
}
// Invert order of messages
for i, j := 0, len(m)-1; i < j; i, j = i+1, j-1 {
m[i], m[j] = m[j], m[i]
}
return m
}
// GetMessagesFromUser returns chat messages that were sent by a specific user.
func (r *SqlChatMessageRepository) GetMessagesFromUser(userID string) ([]events.UserMessageEvent, error) {
query, err := r.datastore.GetQueries().GetMessagesFromUser(context.Background(), sql.NullString{String: userID, Valid: true})
if err != nil {
return nil, err
}
results := make([]events.UserMessageEvent, len(query))
for i, row := range query {
results[i] = events.UserMessageEvent{
Event: events.Event{
Timestamp: row.Timestamp.Time,
ID: row.ID,
},
MessageEvent: events.MessageEvent{
Body: row.Body.String,
},
}
}
return results, nil
}
// GetMessageIdsForUserID will return the chat message IDs for a specific user.
func (r *SqlChatMessageRepository) GetMessageIdsForUserID(userID string) ([]string, error) {
defer func() {
_historyCache = nil
}()
tx, err := r.datastore.DB.Begin()
if err != nil {
return nil, errors.New("error while setting message visibility")
}
defer tx.Rollback() // nolint
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id WHERE user_id IS ?"
stmt, err := tx.Prepare(query)
if err != nil {
return nil, errors.New("error while setting message visibility")
}
rows, err := stmt.Query(userID)
if err != nil {
log.Errorln("error while setting message visibility", err)
return nil, errors.New("error while setting message visibility")
}
defer stmt.Close()
defer rows.Close()
// Get a list of IDs to send to the connected clients to hide
ids := make([]string, 0)
messages, err := getChat(rows)
if err != nil {
log.Errorln(err)
return nil, errors.New("There is a problem enumerating chat message rows. Please report this: " + query)
}
if len(messages) == 0 {
return []string{}, nil
}
for _, message := range messages {
ids = append(ids, message.(events.UserMessageEvent).ID)
}
if err = tx.Commit(); err != nil {
return nil, errors.New("error while setting message visibility")
}
// Tell the clients to hide/show these messages.
return ids, nil
}
func (r *SqlChatMessageRepository) SetMessageVisibilityForMessageIDs(messageIDs []string, visible bool) error {
defer func() {
_historyCache = nil
}()
if len(messageIDs) == 0 {
return nil
}
r.datastore.DbLock.Lock()
defer r.datastore.DbLock.Unlock()
tx, err := r.datastore.DB.Begin()
if err != nil {
return err
}
// nolint:gosec
stmt, err := tx.Prepare("UPDATE messages SET hidden_at=? WHERE id IN (?" + strings.Repeat(",?", len(messageIDs)-1) + ")")
if err != nil {
return err
}
defer stmt.Close()
var hiddenAt *time.Time
if !visible {
now := time.Now()
hiddenAt = &now
} else {
hiddenAt = nil
}
args := make([]interface{}, len(messageIDs)+1)
args[0] = hiddenAt
for i, id := range messageIDs {
args[i+1] = id
}
if _, err = stmt.Exec(args...); err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
return nil
}
// GetMessagesCount will return the number of messages in the database.
func (r *SqlChatMessageRepository) GetMessagesCount() int64 {
query := `SELECT COUNT(*) FROM messages`
rows, err := r.datastore.DB.Query(query)
if err != nil || rows.Err() != nil {
return 0
}
defer rows.Close()
var count int64
for rows.Next() {
if err := rows.Scan(&count); err != nil {
return 0
}
}
return count
}
@@ -4,7 +4,7 @@ import (
"strings"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/webserver/handlers/generated"
log "github.com/sirupsen/logrus"
)
@@ -63,8 +63,9 @@ func migrateToDatastoreValues2(datastore *data.Datastore, configRepository Confi
oldAdminPassword, _ := datastore.GetString("stream_key")
// Avoids double hashing the password
_ = datastore.SetString("admin_password_key", oldAdminPassword)
_ = configRepository.SetStreamKeys([]models.StreamKey{
{Key: oldAdminPassword, Comment: "Default stream key"},
comment := "Default stream key"
_ = configRepository.SetStreamKeys([]generated.StreamKey{
{Key: &oldAdminPassword, Comment: &comment},
})
}
@@ -5,6 +5,7 @@ import (
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/webserver/handlers/generated"
)
type ConfigRepository interface {
@@ -114,8 +115,8 @@ type ConfigRepository interface {
SetCustomOfflineMessage(message string) error
SetCustomColorVariableValues(variables map[string]string) error
GetCustomColorVariableValues() map[string]string
GetStreamKeys() []models.StreamKey
SetStreamKeys(actions []models.StreamKey) error
GetStreamKeys() []generated.StreamKey
SetStreamKeys(actions []generated.StreamKey) error
SetDisableSearchIndexing(disableSearchIndexing bool) error
GetDisableSearchIndexing() bool
GetVideoServingEndpoint() string
@@ -12,6 +12,7 @@ import (
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/static"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/webserver/handlers/generated"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
@@ -957,22 +958,22 @@ func (r *SqlConfigRepository) GetCustomColorVariableValues() map[string]string {
}
// GetStreamKeys will return valid stream keys.
func (r *SqlConfigRepository) GetStreamKeys() []models.StreamKey {
func (r *SqlConfigRepository) GetStreamKeys() []generated.StreamKey {
configEntry, err := r.datastore.Get(streamKeysKey)
if err != nil {
return []models.StreamKey{}
return []generated.StreamKey{}
}
var streamKeys []models.StreamKey
var streamKeys []generated.StreamKey
if err := configEntry.GetObject(&streamKeys); err != nil {
return []models.StreamKey{}
return []generated.StreamKey{}
}
return streamKeys
}
// SetStreamKeys will set valid stream keys.
func (r *SqlConfigRepository) SetStreamKeys(actions []models.StreamKey) error {
func (r *SqlConfigRepository) SetStreamKeys(actions []generated.StreamKey) error {
configEntry := models.ConfigEntry{Key: streamKeysKey, Value: actions}
return r.datastore.Save(configEntry)
}
+17
View File
@@ -0,0 +1,17 @@
package tables
import (
"database/sql"
"github.com/owncast/owncast/utils"
)
func CreateConfigTable(db *sql.DB) {
createTableSQL := `CREATE TABLE IF NOT EXISTS datastore (
"key" string NOT NULL PRIMARY KEY,
"value" BLOB,
"timestamp" DATE DEFAULT CURRENT_TIMESTAMP NOT NULL
);`
utils.MustExec(createTableSQL, db)
}
+28
View File
@@ -0,0 +1,28 @@
package tables
import (
"database/sql"
log "github.com/sirupsen/logrus"
)
func CreateWebhooksTable(db *sql.DB) {
log.Traceln("Creating webhooks table...")
createTableSQL := `CREATE TABLE IF NOT EXISTS webhooks (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"url" string NOT NULL,
"events" TEXT NOT NULL,
"timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP,
"last_used" DATETIME
);`
stmt, err := db.Prepare(createTableSQL)
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
if _, err = stmt.Exec(); err != nil {
log.Warnln(err)
}
}
@@ -1,4 +1,4 @@
package data
package webhookrepository
import (
"errors"
@@ -6,38 +6,51 @@ import (
"strings"
"time"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
log "github.com/sirupsen/logrus"
)
func createWebhooksTable() {
log.Traceln("Creating webhooks table...")
createTableSQL := `CREATE TABLE IF NOT EXISTS webhooks (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"url" string NOT NULL,
"events" TEXT NOT NULL,
"timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP,
"last_used" DATETIME
);`
stmt, err := _db.Prepare(createTableSQL)
if err != nil {
log.Fatal(err)
type WebhookRepository interface {
InsertWebhook(url string, events []models.EventType) (int, error)
DeleteWebhook(id int) error
GetWebhooksForEvent(event models.EventType) []models.Webhook
GetWebhooks() ([]models.Webhook, error)
SetWebhookAsUsed(webhook models.Webhook) error
}
defer stmt.Close()
if _, err = stmt.Exec(); err != nil {
log.Warnln(err)
type SqlWebhookRepository struct {
datastore *data.Datastore
}
// NOTE: This is temporary during the transition period.
var temporaryGlobalInstance WebhookRepository
// Get will return the user repository.
func Get() WebhookRepository {
if temporaryGlobalInstance == nil {
i := New(data.GetDatastore())
temporaryGlobalInstance = i
}
return temporaryGlobalInstance
}
// New will create a new instance of the UserRepository.
func New(datastore *data.Datastore) WebhookRepository {
r := SqlWebhookRepository{
datastore: datastore,
}
return &r
}
// InsertWebhook will add a new webhook to the database.
func InsertWebhook(url string, events []models.EventType) (int, error) {
func (r *SqlWebhookRepository) InsertWebhook(url string, events []models.EventType) (int, error) {
log.Traceln("Adding new webhook")
eventsString := strings.Join(events, ",")
tx, err := _db.Begin()
tx, err := r.datastore.DB.Begin()
if err != nil {
return 0, err
}
@@ -65,10 +78,10 @@ func InsertWebhook(url string, events []models.EventType) (int, error) {
}
// DeleteWebhook will delete a webhook from the database.
func DeleteWebhook(id int) error {
func (r *SqlWebhookRepository) DeleteWebhook(id int) error {
log.Traceln("Deleting webhook")
tx, err := _db.Begin()
tx, err := r.datastore.DB.Begin()
if err != nil {
return err
}
@@ -96,7 +109,7 @@ func DeleteWebhook(id int) error {
}
// GetWebhooksForEvent will return all of the webhooks that want to be notified about an event type.
func GetWebhooksForEvent(event models.EventType) []models.Webhook {
func (r *SqlWebhookRepository) GetWebhooksForEvent(event models.EventType) []models.Webhook {
webhooks := make([]models.Webhook, 0)
query := `SELECT * FROM (
@@ -111,9 +124,9 @@ func GetWebhooksForEvent(event models.EventType) []models.Webhook {
SELECT id, url, event
FROM split
WHERE event <> ''
) AS webhook WHERE event IS "` + event + `"`
) AS webhook WHERE event IS ?`
rows, err := _db.Query(query)
rows, err := r.datastore.DB.Query(query, event)
if err != nil || rows.Err() != nil {
log.Fatal(err)
}
@@ -140,12 +153,12 @@ func GetWebhooksForEvent(event models.EventType) []models.Webhook {
}
// GetWebhooks will return all the webhooks.
func GetWebhooks() ([]models.Webhook, error) { //nolint
func (r *SqlWebhookRepository) GetWebhooks() ([]models.Webhook, error) { //nolint
webhooks := make([]models.Webhook, 0)
query := "SELECT * FROM webhooks"
rows, err := _db.Query(query)
rows, err := r.datastore.DB.Query(query)
if err != nil {
return webhooks, err
}
@@ -193,8 +206,8 @@ func GetWebhooks() ([]models.Webhook, error) { //nolint
}
// SetWebhookAsUsed will update the last used time for a webhook.
func SetWebhookAsUsed(webhook models.Webhook) error {
tx, err := _db.Begin()
func (r *SqlWebhookRepository) SetWebhookAsUsed(webhook models.Webhook) error {
tx, err := r.datastore.DB.Begin()
if err != nil {
return err
}
+2 -2
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4619],{54619:function(e,t,o){"use strict";o.r(t),o.d(t,{NotifyReminderPopup:function(){return h}});var n=o(85893),s=o(67294),r=o(5152),c=o.n(r),i=o(37039),l=o(4330),a=o.n(l),p=o(62960),u=o.n(p);let d=e=>{let{open:t,title:o,content:s,children:r}=e;return(0,n.jsxs)("div",{style:{width:"max-content",height:"max-content"},children:[t&&(0,n.jsx)("div",{className:u().anchor,children:(0,n.jsxs)("div",{className:u().popover,children:[(0,n.jsx)("div",{className:u().title,children:o}),(0,n.jsx)("hr",{style:{color:"var(--color-owncast-palette-4)"}}),(0,n.jsx)("div",{className:u().content,children:s})]})}),r]})},_=c()(()=>o.e(2155).then(o.t.bind(o,12155,23)),{loadableGenerated:{webpack:()=>[12155]},ssr:!1}),h=e=>{let{children:t,open:o,notificationClicked:r,notificationClosed:c}=e,[l,p]=(0,s.useState)(o),[u,h]=(0,s.useState)(!1),{t:m}=(0,i.$G)();(0,s.useEffect)(()=>{p(o)},[o]),(0,s.useEffect)(()=>{h(!0)},[]);let v=(0,n.jsx)("div",{className:a().title,children:m("Stay updated!")}),b=e=>{e.stopPropagation(),r()},x=(0,n.jsxs)("div",{onClick:b,onKeyDown:b,role:"menuitem",tabIndex:0,children:[(0,n.jsx)("button",{type:"button","aria-label":"Follow",className:a().closebutton,onClick:e=>{e.stopPropagation(),p(!1),c()},children:(0,n.jsx)(_,{})}),(0,n.jsx)("div",{className:a().contentbutton,children:m("Click and never miss future streams!")})]});return u&&(0,n.jsx)(d,{open:l,title:v,content:x,children:t})}},4330:function(e){e.exports={popupBackgroundColor:"var(--theme-color-components-primary-button-background)",contentbutton:"NotifyReminderPopup_contentbutton___iqOh",closebutton:"NotifyReminderPopup_closebutton__dpvj4",title:"NotifyReminderPopup_title__imysF"}},62960:function(e){e.exports={anchor:"Popover_anchor__GI7l_",popover:"Popover_popover__pMNs7",title:"Popover_title__T__E6",content:"Popover_content__7gDLm"}}}]);
@@ -1 +0,0 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4619],{54619:function(e,t,o){"use strict";o.r(t),o.d(t,{NotifyReminderPopup:function(){return _}});var n=o(85893),s=o(67294),r=o(5152),c=o.n(r),i=o(4330),l=o.n(i),a=o(62960),p=o.n(a);let u=e=>{let{open:t,title:o,content:s,children:r}=e;return(0,n.jsxs)("div",{style:{width:"max-content",height:"max-content"},children:[t&&(0,n.jsx)("div",{className:p().anchor,children:(0,n.jsxs)("div",{className:p().popover,children:[(0,n.jsx)("div",{className:p().title,children:o}),(0,n.jsx)("hr",{style:{color:"var(--color-owncast-palette-4)"}}),(0,n.jsx)("div",{className:p().content,children:s})]})}),r]})},d=c()(()=>o.e(2155).then(o.t.bind(o,12155,23)),{loadableGenerated:{webpack:()=>[12155]},ssr:!1}),_=e=>{let{children:t,open:o,notificationClicked:r,notificationClosed:c}=e,[i,a]=(0,s.useState)(o),[p,_]=(0,s.useState)(!1);(0,s.useEffect)(()=>{a(o)},[o]),(0,s.useEffect)(()=>{_(!0)},[]);let h=(0,n.jsx)("div",{className:l().title,children:"Stay updated!"}),m=e=>{e.stopPropagation(),r()},v=(0,n.jsxs)("div",{onClick:m,onKeyDown:m,role:"menuitem",tabIndex:0,children:[(0,n.jsx)("button",{type:"button","aria-label":"Follow",className:l().closebutton,onClick:e=>{e.stopPropagation(),a(!1),c()},children:(0,n.jsx)(d,{})}),(0,n.jsx)("div",{className:l().contentbutton,children:"Click and never miss future streams!"})]});return p&&(0,n.jsx)(u,{open:i,title:h,content:v,children:t})}},4330:function(e){e.exports={popupBackgroundColor:"var(--theme-color-components-primary-button-background)",contentbutton:"NotifyReminderPopup_contentbutton___iqOh",closebutton:"NotifyReminderPopup_closebutton__dpvj4",title:"NotifyReminderPopup_title__imysF"}},62960:function(e){e.exports={anchor:"Popover_anchor__GI7l_",popover:"Popover_popover__pMNs7",title:"Popover_title__T__E6",content:"Popover_content__7gDLm"}}}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[8142],{22367:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default={icon:{tag:"svg",attrs:{"fill-rule":"evenodd",viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"}}]},name:"close-circle",theme:"outlined"}},18142:function(e,t,r){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a,l=(a=r(22284))&&a.__esModule?a:{default:a};t.default=l,e.exports=l},22284:function(e,t,r){var a=r(64836),l=r(18698);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n=a(r(42122)),u=function(e,t){if(e&&e.__esModule)return e;if(null===e||"object"!=l(e)&&"function"!=typeof e)return{default:e};var r=o(void 0);if(r&&r.has(e))return r.get(e);var a={__proto__:null},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var u in e)if("default"!==u&&({}).hasOwnProperty.call(e,u)){var c=n?Object.getOwnPropertyDescriptor(e,u):null;c&&(c.get||c.set)?Object.defineProperty(a,u,c):a[u]=e[u]}return a.default=e,r&&r.set(e,a),a}(r(67294)),c=a(r(22367)),f=a(r(3247));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(o=function(e){return e?r:t})(e)}var i=u.forwardRef(function(e,t){return u.createElement(f.default,(0,n.default)((0,n.default)({},e),{},{ref:t,icon:c.default}))});t.default=i}}]);
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[8142],{74644:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default={icon:{tag:"svg",attrs:{"fill-rule":"evenodd",viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm0 76c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm128.01 198.83c.03 0 .05.01.09.06l45.02 45.01a.2.2 0 01.05.09.12.12 0 010 .07c0 .02-.01.04-.05.08L557.25 512l127.87 127.86a.27.27 0 01.05.06v.02a.12.12 0 010 .07c0 .03-.01.05-.05.09l-45.02 45.02a.2.2 0 01-.09.05.12.12 0 01-.07 0c-.02 0-.04-.01-.08-.05L512 557.25 384.14 685.12c-.04.04-.06.05-.08.05a.12.12 0 01-.07 0c-.03 0-.05-.01-.09-.05l-45.02-45.02a.2.2 0 01-.05-.09.12.12 0 010-.07c0-.02.01-.04.06-.08L466.75 512 338.88 384.14a.27.27 0 01-.05-.06l-.01-.02a.12.12 0 010-.07c0-.03.01-.05.05-.09l45.02-45.02a.2.2 0 01.09-.05.12.12 0 01.07 0c.02 0 .04.01.08.06L512 466.75l127.86-127.86c.04-.05.06-.06.08-.06a.12.12 0 01.07 0z"}}]},name:"close-circle",theme:"outlined"}},18142:function(e,t,r){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a,l=(a=r(22284))&&a.__esModule?a:{default:a};t.default=l,e.exports=l},22284:function(e,t,r){var a=r(64836),l=r(18698);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n=a(r(42122)),u=function(e,t){if(e&&e.__esModule)return e;if(null===e||"object"!=l(e)&&"function"!=typeof e)return{default:e};var r=o(void 0);if(r&&r.has(e))return r.get(e);var a={__proto__:null},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var u in e)if("default"!==u&&({}).hasOwnProperty.call(e,u)){var c=n?Object.getOwnPropertyDescriptor(e,u):null;c&&(c.get||c.set)?Object.defineProperty(a,u,c):a[u]=e[u]}return a.default=e,r&&r.set(e,a),a}(r(67294)),c=a(r(74644)),f=a(r(3247));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(o=function(e){return e?r:t})(e)}var i=u.forwardRef(function(e,t){return u.createElement(f.default,(0,n.default)((0,n.default)({},e),{},{ref:t,icon:c.default}))});t.default=i}}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[6559],{10887:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/chat/emojis",function(){return n(21493)}])},21493:function(e,t,n){"use strict";n.r(t);var i=n(85893),o=n(3816),s=n(23657),a=n(56469),l=n(10227),r=n(78021),c=n(8968),u=n(10647),d=n(6468),m=n(67294),h=n(5152),j=n.n(h),f=n(17586),p=n(11992),g=n(35329),y=n(28700),w=n(70869),x=n(25889);let{Meta:k}=o.default,b=j()(()=>Promise.resolve().then(n.t.bind(n,12155,23)),{loadableGenerated:{webpack:()=>[12155]},ssr:!1}),{Title:E,Paragraph:_}=s.default,T=()=>{let[e,t]=(0,m.useState)([]),[n,s]=(0,m.useState)(!1),[h,j]=(0,m.useState)(null),[x,T]=(0,m.useState)(null),v=null,B=()=>{j(null),clearTimeout(v),v=null};async function Z(){s(!0);try{let e=await (0,p.rQ)("/api/emoji");t(e)}catch(e){console.error("error fetching emojis",e)}s(!1)}async function N(e){let t="/".concat(e.split("/").slice(3).join("/"));console.log(t),s(!0),j((0,y.kg)(y.Jk,"Deleting emoji..."));try{let e=await (0,p.rQ)(p.Ff,{method:"POST",data:{name:t}});if(e instanceof Error)throw e;j((0,y.kg)(y.zv,"Emoji deleted")),v=setTimeout(B,w.sI)}catch(e){j((0,y.kg)(y.Un,"".concat(e))),s(!1),v=setTimeout(B,w.sI)}Z()}async function P(){s(!0);try{j((0,y.kg)(y.Jk,"Converting emoji..."));let e=await new Promise((e,t)=>{if(!g.dr.includes(x.type)){let e="File type is not supported: ".concat(x.type);return t(e)}(0,g.y3)(x,t=>e({name:x.name,url:t}))});j((0,y.kg)(y.Jk,"Uploading emoji..."));let t=await (0,p.rQ)(p.Qc,{method:"POST",data:{name:e.name,data:e.url}});if(t instanceof Error)throw t;j((0,y.kg)(y.zv,"Emoji uploaded successfully!")),Z()}catch(e){j((0,y.kg)(y.Un,"".concat(e)))}v=setTimeout(B,w.sI),s(!1)}return(0,m.useEffect)(()=>{Z()},[]),(0,i.jsxs)("div",{children:[(0,i.jsx)(E,{children:"Emojis"}),(0,i.jsx)(_,{children:"Here you can upload new custom emojis for usage in the chat. When uploading a new emoji, the filename without extension will be used as emoji name. Additionally, emoji names are case-insensitive. For best results, ensure all emoji have unique names."}),(0,i.jsx)("br",{}),(0,i.jsx)(d.Z,{name:"emoji",listType:"picture",className:"emoji-uploader",showUploadList:!1,accept:g.dr.join(","),beforeUpload:T,customRequest:P,disabled:n,children:(0,i.jsx)(a.Z,{type:"primary",disabled:n,children:"Upload new emoji"})}),(0,i.jsx)(f.Z,{status:h}),(0,i.jsx)("br",{}),(0,i.jsx)(l.Z,{children:e.map(e=>(0,i.jsx)(r.Z,{style:{padding:"10px"},children:(0,i.jsx)(o.default,{style:{width:120,marginTop:16},actions:[],children:(0,i.jsx)(k,{description:[(0,i.jsxs)("div",{style:{display:"flex",justifyItems:"center",alignItems:"center",flexDirection:"column",gap:"20px"},children:[(0,i.jsx)(c.Z,{title:e.name,children:(0,i.jsx)(u.C,{style:{height:50,width:50},src:e.url})}),(0,i.jsx)(a.Z,{size:"small",type:"ghost",title:"Delete emoji",style:{position:"absolute",right:0,top:0,height:24,width:24,border:"none",color:"gray"},onClick:()=>N(e.url),icon:(0,i.jsx)(b,{})})]})]})})},e.name))}),(0,i.jsx)("br",{})]})};T.getLayout=function(e){return(0,i.jsx)(x.l,{page:e})},t.default=T},35329:function(e,t,n){"use strict";n.d(t,{Z7:function(){return i},dr:function(){return o},kR:function(){return a},y3:function(){return s}});let i=2097152,o=["image/png","image/jpeg","image/gif"];function s(e,t){let n=new FileReader;n.addEventListener("load",()=>t(n.result)),n.readAsDataURL(e)}function a(e){let t=Math.floor(Math.log(e)/Math.log(1024)),n=1*Number((e/Math.pow(1024,t)).toFixed(2));return"".concat(n," ").concat(["B","KB","MB","GB","TB","PB","EB","ZB","YB"][t])}}},function(e){e.O(0,[3247,83,1287,3800,7786,443,9904,3657,6167,2502,9307,2179,6356,1616,5889,2888,9774,179],function(){return e(e.s=10887)}),_N_E=e.O()}]);
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[6559],{10887:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/chat/emojis",function(){return n(21493)}])},21493:function(e,t,n){"use strict";n.r(t);var i=n(85893),o=n(3816),s=n(23657),a=n(56469),l=n(10227),r=n(78021),c=n(8968),u=n(10647),d=n(6468),m=n(67294),h=n(5152),j=n.n(h),f=n(17586),p=n(11992),g=n(35329),y=n(28700),w=n(70869),x=n(25889);let{Meta:k}=o.default,b=j()(()=>Promise.resolve().then(n.t.bind(n,12155,23)),{loadableGenerated:{webpack:()=>[12155]},ssr:!1}),{Title:E,Paragraph:_}=s.default,T=()=>{let[e,t]=(0,m.useState)([]),[n,s]=(0,m.useState)(!1),[h,j]=(0,m.useState)(null),[x,T]=(0,m.useState)(null),v=null,B=()=>{j(null),clearTimeout(v),v=null};async function Z(){s(!0);try{let e=await (0,p.rQ)("/api/emoji");t(e)}catch(e){console.error("error fetching emojis",e)}s(!1)}async function N(e){let t="/".concat(e.split("/").slice(3).join("/"));console.log(t),s(!0),j((0,y.kg)(y.Jk,"Deleting emoji..."));try{let e=await (0,p.rQ)(p.Ff,{method:"POST",data:{name:t}});if(e instanceof Error)throw e;j((0,y.kg)(y.zv,"Emoji deleted")),v=setTimeout(B,w.sI)}catch(e){j((0,y.kg)(y.Un,"".concat(e))),s(!1),v=setTimeout(B,w.sI)}Z()}async function P(){s(!0);try{j((0,y.kg)(y.Jk,"Converting emoji..."));let e=await new Promise((e,t)=>{if(!g.dr.includes(x.type)){let e="File type is not supported: ".concat(x.type);return t(e)}(0,g.y3)(x,t=>e({name:x.name,url:t}))});j((0,y.kg)(y.Jk,"Uploading emoji..."));let t=await (0,p.rQ)(p.Qc,{method:"POST",data:{name:e.name,data:e.url}});if(t instanceof Error)throw t;j((0,y.kg)(y.zv,"Emoji uploaded successfully!")),Z()}catch(e){j((0,y.kg)(y.Un,"".concat(e)))}v=setTimeout(B,w.sI),s(!1)}return(0,m.useEffect)(()=>{Z()},[]),(0,i.jsxs)("div",{children:[(0,i.jsx)(E,{children:"Emojis"}),(0,i.jsx)(_,{children:"Here you can upload new custom emojis for usage in the chat. When uploading a new emoji, the filename without extension will be used as emoji name. Additionally, emoji names are case-insensitive. For best results, ensure all emoji have unique names."}),(0,i.jsx)("br",{}),(0,i.jsx)(d.Z,{name:"emoji",listType:"picture",className:"emoji-uploader",showUploadList:!1,accept:g.dr.join(","),beforeUpload:T,customRequest:P,disabled:n,children:(0,i.jsx)(a.Z,{type:"primary",disabled:n,children:"Upload new emoji"})}),(0,i.jsx)(f.Z,{status:h}),(0,i.jsx)("br",{}),(0,i.jsx)(l.Z,{children:e.map(e=>(0,i.jsx)(r.Z,{style:{padding:"10px"},children:(0,i.jsx)(o.default,{style:{width:120,marginTop:16},actions:[],children:(0,i.jsx)(k,{description:[(0,i.jsxs)("div",{style:{display:"flex",justifyItems:"center",alignItems:"center",flexDirection:"column",gap:"20px"},children:[(0,i.jsx)(c.Z,{title:e.name,children:(0,i.jsx)(u.C,{style:{height:50,width:50},src:e.url})}),(0,i.jsx)(a.Z,{size:"small",type:"ghost",title:"Delete emoji",style:{position:"absolute",right:0,top:0,height:24,width:24,border:"none",color:"gray"},onClick:()=>N(e.url),icon:(0,i.jsx)(b,{})})]})]})})},e.name))}),(0,i.jsx)("br",{})]})};T.getLayout=function(e){return(0,i.jsx)(x.l,{page:e})},t.default=T},35329:function(e,t,n){"use strict";n.d(t,{Z7:function(){return i},dr:function(){return o},kR:function(){return a},y3:function(){return s}});let i=2097152,o=["image/png","image/jpeg","image/gif"];function s(e,t){let n=new FileReader;n.addEventListener("load",()=>t(n.result)),n.readAsDataURL(e)}function a(e){let t=Math.floor(Math.log(e)/Math.log(1024)),n=1*Number((e/Math.pow(1024,t)).toFixed(2));return"".concat(n," ").concat(["B","KB","MB","GB","TB","PB","EB","ZB","YB"][t])}}},function(e){e.O(0,[3247,83,1287,3800,7786,443,9904,3657,1175,2502,9307,7987,6356,1616,5889,2888,9774,179],function(){return e(e.s=10887)}),_N_E=e.O()}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4976],{10203:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/federation/actions",function(){return n(57679)}])},64773:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(97685),i=n(67294);function u(){var e=i.useReducer(function(e){return e+1},0);return(0,r.Z)(e,2)[1]}},23061:function(e,t,n){"use strict";var r=n(67294),i=n(64773),u=n(98947);t.Z=function(){var e=!(arguments.length>0)||void 0===arguments[0]||arguments[0],t=(0,r.useRef)({}),n=(0,i.Z)();return(0,r.useEffect)(function(){var r=u.ZP.subscribe(function(r){t.current=r,e&&n()});return function(){return u.ZP.unsubscribe(r)}},[]),t.current}},65765:function(e,t,n){"use strict";var r=n(61185),i=n(59408),u=r.ZP;u.Header=r.h4,u.Footer=r.$_,u.Content=r.VY,u.Sider=i.Z,u._InternalSiderContext=i.D,t.default=u},57679:function(e,t,n){"use strict";n.r(t),n.d(t,{default:function(){return h}});var r=n(85893),i=n(67294),u=n(23657),o=n(27043),c=n(12642),s=n(11992),a=n(74040),l=n(25889);let{Title:f,Paragraph:d}=u.default;function h(){let[e,t]=(0,i.useState)([]),[n,u]=(0,i.useState)(0),[l,h]=(0,i.useState)(0),E=async()=>{try{let e="".concat(s.op,"?offset=").concat(50*l,"&limit=").concat(50),{results:n,total:r}=await (0,s.rQ)(e,{auth:!0});u(r),(0,a.Qr)(n)?t([]):t(n)}catch(e){console.log("==== error",e)}};(0,i.useEffect)(()=>{E()},[l]);let p=[{title:"Action",dataIndex:"type",key:"type",width:50,render:(e,t)=>{let n,i;switch(t.type){case"FEDIVERSE_ENGAGEMENT_REPOST":n="/img/repost.svg",i="Share";break;case"FEDIVERSE_ENGAGEMENT_LIKE":n="/img/like.svg",i="Like";break;case"FEDIVERSE_ENGAGEMENT_FOLLOW":n="/img/follow.svg",i="Follow";break;default:n=""}return(0,r.jsxs)("div",{style:{width:"100%",height:"100%",display:"flex",alignItems:"center",justifyContent:"center",flexDirection:"column"},children:[(0,r.jsx)("img",{src:n,width:"70%",alt:i,title:i}),(0,r.jsx)("div",{style:{fontSize:"0.7rem"},children:i})]})}},{title:"From",dataIndex:"actorIRI",key:"from",render:(e,t)=>(0,r.jsx)("a",{href:t.actorIRI,children:t.actorIRI})},{title:"When",dataIndex:"timestamp",key:"timestamp",render:(e,t)=>{let n=new Date(t.timestamp);return(0,c.WU)(n,"P pp")}}];return(0,r.jsxs)("div",{children:[(0,r.jsx)(f,{level:3,children:"Fediverse Actions"}),(0,r.jsx)(d,{children:"Below is a list of actions that were taken by others in response to your posts as well as people who requested to follow you."}),(0,r.jsx)(o.Z,{dataSource:e,columns:p,size:"small",rowKey:e=>e.iri,pagination:{pageSize:50,hideOnSinglePage:!0,showSizeChanger:!1,total:n},onChange:e=>{h(e.current)}})]})}h.getLayout=function(e){return(0,r.jsx)(l.l,{page:e})}},11163:function(e,t,n){e.exports=n(43079)},55945:function(e,t,n){"use strict";function r(e){return t=>{let n=(e?Math[e]:Math.trunc)(t);return 0===n?0:n}}n.d(t,{u:function(){return r}})},64077:function(e,t,n){"use strict";n.d(t,{_:function(){return i}});var r=n(46042);function i(e,t){return+(0,r.Q)(e)-+(0,r.Q)(t)}},94817:function(e,t,n){"use strict";n.d(t,{c:function(){return u}});var r=n(55945),i=n(64077);function u(e,t,n){let u=(0,i._)(e,t)/1e3;return(0,r.u)(null==n?void 0:n.roundingMethod)(u)}}},function(e){e.O(0,[83,1287,3800,7786,443,9904,3657,6167,2502,7528,9532,449,7043,4065,2642,5889,2888,9774,179],function(){return e(e.s=10203)}),_N_E=e.O()}]);
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4976],{10203:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/federation/actions",function(){return n(57679)}])},64773:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(97685),i=n(67294);function u(){var e=i.useReducer(function(e){return e+1},0);return(0,r.Z)(e,2)[1]}},23061:function(e,t,n){"use strict";var r=n(67294),i=n(64773),u=n(98947);t.Z=function(){var e=!(arguments.length>0)||void 0===arguments[0]||arguments[0],t=(0,r.useRef)({}),n=(0,i.Z)();return(0,r.useEffect)(function(){var r=u.ZP.subscribe(function(r){t.current=r,e&&n()});return function(){return u.ZP.unsubscribe(r)}},[]),t.current}},65765:function(e,t,n){"use strict";var r=n(61185),i=n(59408),u=r.ZP;u.Header=r.h4,u.Footer=r.$_,u.Content=r.VY,u.Sider=i.Z,u._InternalSiderContext=i.D,t.default=u},57679:function(e,t,n){"use strict";n.r(t),n.d(t,{default:function(){return h}});var r=n(85893),i=n(67294),u=n(23657),o=n(27043),c=n(12642),s=n(11992),a=n(74040),l=n(25889);let{Title:f,Paragraph:d}=u.default;function h(){let[e,t]=(0,i.useState)([]),[n,u]=(0,i.useState)(0),[l,h]=(0,i.useState)(0),E=async()=>{try{let e="".concat(s.op,"?offset=").concat(50*l,"&limit=").concat(50),{results:n,total:r}=await (0,s.rQ)(e,{auth:!0});u(r),(0,a.Qr)(n)?t([]):t(n)}catch(e){console.log("==== error",e)}};(0,i.useEffect)(()=>{E()},[l]);let p=[{title:"Action",dataIndex:"type",key:"type",width:50,render:(e,t)=>{let n,i;switch(t.type){case"FEDIVERSE_ENGAGEMENT_REPOST":n="/img/repost.svg",i="Share";break;case"FEDIVERSE_ENGAGEMENT_LIKE":n="/img/like.svg",i="Like";break;case"FEDIVERSE_ENGAGEMENT_FOLLOW":n="/img/follow.svg",i="Follow";break;default:n=""}return(0,r.jsxs)("div",{style:{width:"100%",height:"100%",display:"flex",alignItems:"center",justifyContent:"center",flexDirection:"column"},children:[(0,r.jsx)("img",{src:n,width:"70%",alt:i,title:i}),(0,r.jsx)("div",{style:{fontSize:"0.7rem"},children:i})]})}},{title:"From",dataIndex:"actorIRI",key:"from",render:(e,t)=>(0,r.jsx)("a",{href:t.actorIRI,children:t.actorIRI})},{title:"When",dataIndex:"timestamp",key:"timestamp",render:(e,t)=>{let n=new Date(t.timestamp);return(0,c.WU)(n,"P pp")}}];return(0,r.jsxs)("div",{children:[(0,r.jsx)(f,{level:3,children:"Fediverse Actions"}),(0,r.jsx)(d,{children:"Below is a list of actions that were taken by others in response to your posts as well as people who requested to follow you."}),(0,r.jsx)(o.Z,{dataSource:e,columns:p,size:"small",rowKey:e=>e.iri,pagination:{pageSize:50,hideOnSinglePage:!0,showSizeChanger:!1,total:n},onChange:e=>{h(e.current)}})]})}h.getLayout=function(e){return(0,r.jsx)(l.l,{page:e})}},55945:function(e,t,n){"use strict";function r(e){return t=>{let n=(e?Math[e]:Math.trunc)(t);return 0===n?0:n}}n.d(t,{u:function(){return r}})},64077:function(e,t,n){"use strict";n.d(t,{_:function(){return i}});var r=n(46042);function i(e,t){return+(0,r.Q)(e)-+(0,r.Q)(t)}},94817:function(e,t,n){"use strict";n.d(t,{c:function(){return u}});var r=n(55945),i=n(64077);function u(e,t,n){let u=(0,i._)(e,t)/1e3;return(0,r.u)(null==n?void 0:n.roundingMethod)(u)}}},function(e){e.O(0,[83,1287,3800,7786,443,9904,3657,1175,2502,7528,9532,449,7043,4065,2642,5889,2888,9774,179],function(){return e(e.s=10203)}),_N_E=e.O()}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9262],{70918:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/upgrade",function(){return n(26802)}])},26802:function(e,t,n){"use strict";n.r(t);var a=n(85893),r=n(67294),l=n(65891),s=n(23657),d=n(27043),u=n(11992),i=n(25889);let{Title:c}=s.default,o=e=>{let t=Object.values(e);return(0,a.jsx)(d.Z,{dataSource:t,columns:[{title:"Name",dataIndex:"name",key:"name",render:(e,t)=>(0,a.jsx)("a",{href:t.browser_download_url,children:e})},{title:"Size",dataIndex:"size",key:"size",render:e=>"".concat((e/1024/1024).toFixed(2)," MB")}],rowKey:e=>e.id,size:"large",pagination:!1})},_=()=>{let[e,t]=(0,r.useState)({html_url:"",name:"",created_at:null,body:"",assets:[]}),n=async()=>{try{let e=await (0,u.Kt)();t(e)}catch(e){console.log("==== error",e)}};return((0,r.useEffect)(()=>{n()},[]),e)?(0,a.jsxs)("div",{className:"upgrade-page",children:[(0,a.jsx)(c,{level:2,children:(0,a.jsx)("a",{href:e.html_url,children:e.name})}),(0,a.jsx)(c,{level:5,children:new Date(e.created_at).toDateString()}),(0,a.jsx)(l.U,{children:e.body}),(0,a.jsx)("h3",{children:"Downloads"}),(0,a.jsx)(o,{...e.assets})]}):null};_.getLayout=function(e){return(0,a.jsx)(i.l,{page:e})},t.default=_}},function(e){e.O(0,[83,1287,3800,7786,443,9904,3657,6167,2502,7528,9532,449,7043,4009,5889,2888,9774,179],function(){return e(e.s=70918)}),_N_E=e.O()}]);
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9262],{70918:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/upgrade",function(){return n(26802)}])},26802:function(e,t,n){"use strict";n.r(t);var a=n(85893),r=n(67294),l=n(65891),s=n(23657),d=n(27043),u=n(11992),i=n(25889);let{Title:c}=s.default,o=e=>{let t=Object.values(e);return(0,a.jsx)(d.Z,{dataSource:t,columns:[{title:"Name",dataIndex:"name",key:"name",render:(e,t)=>(0,a.jsx)("a",{href:t.browser_download_url,children:e})},{title:"Size",dataIndex:"size",key:"size",render:e=>"".concat((e/1024/1024).toFixed(2)," MB")}],rowKey:e=>e.id,size:"large",pagination:!1})},_=()=>{let[e,t]=(0,r.useState)({html_url:"",name:"",created_at:null,body:"",assets:[]}),n=async()=>{try{let e=await (0,u.Kt)();t(e)}catch(e){console.log("==== error",e)}};return((0,r.useEffect)(()=>{n()},[]),e)?(0,a.jsxs)("div",{className:"upgrade-page",children:[(0,a.jsx)(c,{level:2,children:(0,a.jsx)("a",{href:e.html_url,children:e.name})}),(0,a.jsx)(c,{level:5,children:new Date(e.created_at).toDateString()}),(0,a.jsx)(l.U,{children:e.body}),(0,a.jsx)("h3",{children:"Downloads"}),(0,a.jsx)(o,{...e.assets})]}):null};_.getLayout=function(e){return(0,a.jsx)(i.l,{page:e})},t.default=_}},function(e){e.O(0,[83,1287,3800,7786,443,9904,3657,1175,2502,7528,9532,449,7043,2122,5889,2888,9774,179],function(){return e(e.s=70918)}),_N_E=e.O()}]);

Some files were not shown because too many files have changed in this diff Show More