From 07c5cabfe8b9c9babb85c6b89c61c7c05cd3c638 Mon Sep 17 00:00:00 2001 From: Pranav Joglekar Date: Wed, 29 Mar 2023 00:04:00 +0530 Subject: [PATCH] Trigger the title updated webhook event whenever a title is changed (#2823) * wip: trigger the title updated webhook event whenever a title is changed * Commit updated API documentation * fix: add STREAM_TITLE_CHANGED to list of valid events * feat: Add support for STREAM_TITLE_CHANGED webhook event on admin dashboard * fix: transmit webhook event after stream has changed to fix race conditions where older title was sent --------- Co-authored-by: Owncast --- controllers/admin/config.go | 2 ++ core/chat/events/eventtype.go | 2 ++ docs/api/index.html | 6 +++--- models/eventType.go | 2 ++ models/webhook.go | 1 + web/pages/admin/webhooks.tsx | 5 +++++ 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/controllers/admin/config.go b/controllers/admin/config.go index 9e0335e11..f7c5af058 100644 --- a/controllers/admin/config.go +++ b/controllers/admin/config.go @@ -15,6 +15,7 @@ import ( "github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/user" + "github.com/owncast/owncast/core/webhooks" "github.com/owncast/owncast/models" "github.com/owncast/owncast/utils" log "github.com/sirupsen/logrus" @@ -75,6 +76,7 @@ func SetStreamTitle(w http.ResponseWriter, r *http.Request) { } if value != "" { sendSystemChatAction(fmt.Sprintf("Stream title changed to **%s**", value), true) + go webhooks.SendStreamStatusEvent(models.StreamTitleUpdated) } controllers.WriteSimpleResponse(w, true, "changed") } diff --git a/core/chat/events/eventtype.go b/core/chat/events/eventtype.go index 2026c85c6..3bfde1f8a 100644 --- a/core/chat/events/eventtype.go +++ b/core/chat/events/eventtype.go @@ -22,6 +22,8 @@ const ( StreamStarted EventType = "STREAM_STARTED" // StreamStopped represents a stream stopped event. StreamStopped EventType = "STREAM_STOPPED" + // StreamTitleUpdated is the event sent when a stream's title changes. + StreamTitleUpdated EventType = "STREAM_TITLE_UPDATED" // SystemMessageSent is the event sent when a system message is sent. SystemMessageSent EventType = "SYSTEM" // ChatDisabled is when a user is explicitly disabled and blocked from using chat. diff --git a/docs/api/index.html b/docs/api/index.html index d0a6cdb0a..969294a7b 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -26,7 +26,7 @@ `;var ga=ua;function ya(e){return t=>{if(t.theme.extensionsHook)return t.theme.extensionsHook(e,t)}}const va=ga.div` padding: 20px; color: red; -`;class ba extends n.Component{constructor(e){super(e),this.state={error:void 0}}componentDidCatch(e){return this.setState({error:e}),!1}render(){return this.state.error?n.createElement(va,null,n.createElement("h1",null,"Something went wrong..."),n.createElement("small",null," ",this.state.error.message," "),n.createElement("p",null,n.createElement("details",null,n.createElement("summary",null,"Stack trace"),n.createElement("pre",null,this.state.error.stack))),n.createElement("small",null," ReDoc Version: ","2.0.0-rc.77")," ",n.createElement("br",null),n.createElement("small",null," Commit: ","580f883")):n.createElement(n.Fragment,null,n.Children.only(this.props.children))}}const wa=fa` +`;class ba extends n.Component{constructor(e){super(e),this.state={error:void 0}}componentDidCatch(e){return this.setState({error:e}),!1}render(){return this.state.error?n.createElement(va,null,n.createElement("h1",null,"Something went wrong..."),n.createElement("small",null," ",this.state.error.message," "),n.createElement("p",null,n.createElement("details",null,n.createElement("summary",null,"Stack trace"),n.createElement("pre",null,this.state.error.stack))),n.createElement("small",null," ReDoc Version: ","2.0.0")," ",n.createElement("br",null),n.createElement("small",null," Commit: ","5fb4daa")):n.createElement(n.Fragment,null,n.Children.only(this.props.children))}}const wa=fa` 0% { transform: rotate(0deg); } 100% { @@ -1816,7 +1816,7 @@ cursor: pointer; font-style: normal; color: '#666'; -`;var Hb=Object.defineProperty,Yb=Object.getOwnPropertyDescriptor;class Kb extends n.PureComponent{constructor(e){super(e),this.activeItemRef=null,this.clear=()=>{this.setState({results:[],noResults:!1,term:"",activeItemIdx:-1}),this.props.marker.unmark()},this.handleKeyDown=e=>{if(27===e.keyCode&&this.clear(),40===e.keyCode&&(this.setState({activeItemIdx:Math.min(this.state.activeItemIdx+1,this.state.results.length-1)}),e.preventDefault()),38===e.keyCode&&(this.setState({activeItemIdx:Math.max(0,this.state.activeItemIdx-1)}),e.preventDefault()),13===e.keyCode){const e=this.state.results[this.state.activeItemIdx];if(e){const t=this.props.getItemById(e.meta);t&&this.props.onActivate(t)}}},this.search=e=>{const{minCharacterLengthToInitSearch:t}=this.context,n=e.target.value;n.lengththis.searchCallback(this.state.term)))},this.state={results:[],noResults:!1,term:"",activeItemIdx:-1}}clearResults(e){this.setState({results:[],noResults:!1,term:e}),this.props.marker.unmark()}setResults(e,t){this.setState({results:e,noResults:0===e.length}),this.props.marker.mark(t)}searchCallback(e){this.props.search.search(e).then((t=>{this.setResults(t,e)}))}render(){const{activeItemIdx:e}=this.state,t=this.state.results.filter((e=>this.props.getItemById(e.meta))).map((e=>({item:this.props.getItemById(e.meta),score:e.score}))).sort(((e,t)=>t.score-e.score));return n.createElement(Ub,{role:"search"},this.state.term&&n.createElement(Wb,{onClick:this.clear},"×"),n.createElement(Bb,null),n.createElement(Vb,{value:this.state.term,onKeyDown:this.handleKeyDown,placeholder:"Search...","aria-label":"Search",type:"text",onChange:this.search}),t.length>0&&n.createElement(Pd,{options:{wheelPropagation:!1}},n.createElement(qb,{"data-role":"search:results"},t.map(((t,r)=>n.createElement(wb,{item:Object.create(t.item,{active:{value:r===e}}),onActivate:this.props.onActivate,withoutChildren:!0,key:t.item.id,"data-role":"search:result"}))))),this.state.term&&this.state.noResults?n.createElement(qb,{"data-role":"search:results"},lo("noResultsFound")):null)}}Kb.contextType=Sa,((e,t,n,r)=>{for(var o,i=Yb(t,n),a=e.length-1;a>=0;a--)(o=e[a])&&(i=o(t,n,i)||i);i&&Hb(t,n,i)})([Ra.bind,(0,Ra.debounce)(400)],Kb.prototype,"searchCallback");class Gb extends n.Component{componentDidMount(){this.props.store.onDidMount()}componentWillUnmount(){this.props.store.dispose()}render(){const{store:{spec:e,menu:t,options:r,search:o,marker:i}}=this.props,a=this.props.store;return n.createElement(ha,{theme:r.theme},n.createElement(Du,{value:a},n.createElement(Ea,{value:r},n.createElement(Mb,{className:"redoc-wrap"},n.createElement(Lb,{menu:t,className:"menu-content"},n.createElement(py,{info:e.info}),!r.disableSearch&&n.createElement(Kb,{search:o,marker:i,getItemById:t.getItemById,onActivate:t.activateAndScroll})||null,n.createElement($b,{menu:t})),n.createElement(Fb,{className:"api-content"},n.createElement(sy,{store:a}),n.createElement(lb,{items:t.items})),n.createElement(zb,null)))))}}Gb.propTypes={store:Oa.instanceOf(ey).isRequired};const Qb=function(e){const{spec:t,specUrl:o,options:i={},onLoaded:a}=e,s=bo(i.hideLoading,!1),l=new xo(i);if(void 0!==l.nonce)try{r.nc=l.nonce}catch(e){}return n.createElement(ba,null,n.createElement(Mu,{spec:t,specUrl:o,options:i,onLoaded:a},(({loading:e,store:t})=>e?s?null:n.createElement(_a,{color:l.theme.colors.primary.main}):n.createElement(Gb,{store:t}))))};var Xb=Object.defineProperty,Jb=Object.getOwnPropertySymbols,Zb=Object.prototype.hasOwnProperty,ew=Object.prototype.propertyIsEnumerable,tw=(e,t,n)=>t in e?Xb(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,nw=(e,t)=>{for(var n in t||(t={}))Zb.call(t,n)&&tw(e,n,t[n]);if(Jb)for(var n of Jb(t))ew.call(t,n)&&tw(e,n,t[n]);return e};Nt({useProxies:"ifavailable"});const rw="2.0.0-rc.77",ow="580f883";function iw(e){const t=function(e){const t={},n=e.attributes;for(let e=0;et.toUpperCase())),o=t[e];n[r]="theme"===e?JSON.parse(o):o}return n}function aw(e,t={},r=Wr("redoc"),o){if(null===r)throw new Error('"element" argument is not provided and tag is not found on the page');let a,s;"string"==typeof e?a=e:"object"==typeof e&&(s=e),(0,i.render)(n.createElement(Qb,{spec:s,onLoaded:o,specUrl:a,options:nw(nw({},t),iw(r))},["Loading..."]),r)}function sw(e=Wr("redoc")){e&&(0,i.unmountComponentAtNode)(e)}function lw(e,t=Wr("redoc"),r){const o=ey.fromJS(e);setTimeout((()=>{(0,i.hydrate)(n.createElement(Gb,{store:o}),t,r)}),0)}!function(){const e=Wr("redoc");if(!e)return;const t=e.getAttribute("spec-url");t&&aw(t,{},e)}()}(),o}()})); +`;var Hb=Object.defineProperty,Yb=Object.getOwnPropertyDescriptor;class Kb extends n.PureComponent{constructor(e){super(e),this.activeItemRef=null,this.clear=()=>{this.setState({results:[],noResults:!1,term:"",activeItemIdx:-1}),this.props.marker.unmark()},this.handleKeyDown=e=>{if(27===e.keyCode&&this.clear(),40===e.keyCode&&(this.setState({activeItemIdx:Math.min(this.state.activeItemIdx+1,this.state.results.length-1)}),e.preventDefault()),38===e.keyCode&&(this.setState({activeItemIdx:Math.max(0,this.state.activeItemIdx-1)}),e.preventDefault()),13===e.keyCode){const e=this.state.results[this.state.activeItemIdx];if(e){const t=this.props.getItemById(e.meta);t&&this.props.onActivate(t)}}},this.search=e=>{const{minCharacterLengthToInitSearch:t}=this.context,n=e.target.value;n.lengththis.searchCallback(this.state.term)))},this.state={results:[],noResults:!1,term:"",activeItemIdx:-1}}clearResults(e){this.setState({results:[],noResults:!1,term:e}),this.props.marker.unmark()}setResults(e,t){this.setState({results:e,noResults:0===e.length}),this.props.marker.mark(t)}searchCallback(e){this.props.search.search(e).then((t=>{this.setResults(t,e)}))}render(){const{activeItemIdx:e}=this.state,t=this.state.results.filter((e=>this.props.getItemById(e.meta))).map((e=>({item:this.props.getItemById(e.meta),score:e.score}))).sort(((e,t)=>t.score-e.score));return n.createElement(Ub,{role:"search"},this.state.term&&n.createElement(Wb,{onClick:this.clear},"×"),n.createElement(Bb,null),n.createElement(Vb,{value:this.state.term,onKeyDown:this.handleKeyDown,placeholder:"Search...","aria-label":"Search",type:"text",onChange:this.search}),t.length>0&&n.createElement(Pd,{options:{wheelPropagation:!1}},n.createElement(qb,{"data-role":"search:results"},t.map(((t,r)=>n.createElement(wb,{item:Object.create(t.item,{active:{value:r===e}}),onActivate:this.props.onActivate,withoutChildren:!0,key:t.item.id,"data-role":"search:result"}))))),this.state.term&&this.state.noResults?n.createElement(qb,{"data-role":"search:results"},lo("noResultsFound")):null)}}Kb.contextType=Sa,((e,t,n,r)=>{for(var o,i=Yb(t,n),a=e.length-1;a>=0;a--)(o=e[a])&&(i=o(t,n,i)||i);i&&Hb(t,n,i)})([Ra.bind,(0,Ra.debounce)(400)],Kb.prototype,"searchCallback");class Gb extends n.Component{componentDidMount(){this.props.store.onDidMount()}componentWillUnmount(){this.props.store.dispose()}render(){const{store:{spec:e,menu:t,options:r,search:o,marker:i}}=this.props,a=this.props.store;return n.createElement(ha,{theme:r.theme},n.createElement(Du,{value:a},n.createElement(Ea,{value:r},n.createElement(Mb,{className:"redoc-wrap"},n.createElement(Lb,{menu:t,className:"menu-content"},n.createElement(py,{info:e.info}),!r.disableSearch&&n.createElement(Kb,{search:o,marker:i,getItemById:t.getItemById,onActivate:t.activateAndScroll})||null,n.createElement($b,{menu:t})),n.createElement(Fb,{className:"api-content"},n.createElement(sy,{store:a}),n.createElement(lb,{items:t.items})),n.createElement(zb,null)))))}}Gb.propTypes={store:Oa.instanceOf(ey).isRequired};const Qb=function(e){const{spec:t,specUrl:o,options:i={},onLoaded:a}=e,s=bo(i.hideLoading,!1),l=new xo(i);if(void 0!==l.nonce)try{r.nc=l.nonce}catch(e){}return n.createElement(ba,null,n.createElement(Mu,{spec:t,specUrl:o,options:i,onLoaded:a},(({loading:e,store:t})=>e?s?null:n.createElement(_a,{color:l.theme.colors.primary.main}):n.createElement(Gb,{store:t}))))};var Xb=Object.defineProperty,Jb=Object.getOwnPropertySymbols,Zb=Object.prototype.hasOwnProperty,ew=Object.prototype.propertyIsEnumerable,tw=(e,t,n)=>t in e?Xb(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,nw=(e,t)=>{for(var n in t||(t={}))Zb.call(t,n)&&tw(e,n,t[n]);if(Jb)for(var n of Jb(t))ew.call(t,n)&&tw(e,n,t[n]);return e};Nt({useProxies:"ifavailable"});const rw="2.0.0",ow="5fb4daa";function iw(e){const t=function(e){const t={},n=e.attributes;for(let e=0;et.toUpperCase())),o=t[e];n[r]="theme"===e?JSON.parse(o):o}return n}function aw(e,t={},r=Wr("redoc"),o){if(null===r)throw new Error('"element" argument is not provided and tag is not found on the page');let a,s;"string"==typeof e?a=e:"object"==typeof e&&(s=e),(0,i.render)(n.createElement(Qb,{spec:s,onLoaded:o,specUrl:a,options:nw(nw({},t),iw(r))},["Loading..."]),r)}function sw(e=Wr("redoc")){e&&(0,i.unmountComponentAtNode)(e)}function lw(e,t=Wr("redoc"),r){const o=ey.fromJS(e);setTimeout((()=>{(0,i.hydrate)(n.createElement(Gb,{store:o}),t,r)}),0)}!function(){const e=Wr("redoc");if(!e)return;const t=e.getAttribute("spec-url");t&&aw(t,{},e)}()}(),o}()})); //# sourceMappingURL=redoc.standalone.js.map