From e6bfe2d4446110b53e4ce357612dc85db1bd0cf1 Mon Sep 17 00:00:00 2001 From: onysd Date: Thu, 3 Sep 2026 05:00:42 +0300 Subject: [PATCH] fix for retention logic --- .env.example | 21 + .../{index-IZM8WmmY.js => index-By5gK7SA.js} | 2 +- cmd/telesrv-admin/web/dist/index.html | 2 +- .../web/src/pages/StoragePage.tsx | 168 +++++++- cmd/telesrv/main.go | 51 ++- cmd/telesrv/main_test.go | 33 ++ ...20260902000001_documents_category.down.sql | 3 + .../20260902000001_documents_category.up.sql | 18 + ...account_settings_disallowed_gifts.down.sql | 6 + ...1_account_settings_disallowed_gifts.up.sql | 15 + internal/app/channels/service.go | 16 + internal/app/files/retention.go | 312 +++++++++++---- .../app/files/retention_category_zero_test.go | 153 ++++++++ internal/app/files/retention_purge.go | 204 ++++++++++ internal/app/files/retention_purge_test.go | 29 ++ internal/app/files/service.go | 42 ++ internal/app/maintenance/retention.go | 64 +++- internal/config/config.go | 70 +++- internal/config/config_test.go | 23 +- internal/domain/channel.go | 15 + internal/domain/media.go | 7 + internal/domain/media_category.go | 20 + internal/domain/media_refs.go | 12 + internal/domain/message.go | 11 + internal/rpc/convert_channels_core.go | 2 + internal/rpc/convert_messages.go | 2 + .../store/postgres/channel_message_edit.go | 98 ++++- .../media_hard_retention_integration_test.go | 4 +- internal/store/postgres/media_index.go | 24 ++ internal/store/postgres/media_refs.go | 105 ++++- internal/store/postgres/message_edit.go | 11 +- internal/store/postgres/queries/media.sql | 92 ++++- .../postgres/retention_v2_integration_test.go | 360 ++++++++++++++++++ internal/store/postgres/sqlcgen/media.sql.go | 244 +++++++++++- internal/store/postgres/sqlcgen/models.go | 1 + 35 files changed, 2108 insertions(+), 132 deletions(-) rename cmd/telesrv-admin/web/dist/assets/{index-IZM8WmmY.js => index-By5gK7SA.js} (89%) create mode 100644 deploy/migrations/20260902000001_documents_category.down.sql create mode 100644 deploy/migrations/20260902000001_documents_category.up.sql create mode 100644 deploy/migrations/20260903000001_account_settings_disallowed_gifts.down.sql create mode 100644 deploy/migrations/20260903000001_account_settings_disallowed_gifts.up.sql create mode 100644 internal/app/files/retention_category_zero_test.go create mode 100644 internal/app/files/retention_purge.go create mode 100644 internal/app/files/retention_purge_test.go create mode 100644 internal/store/postgres/retention_v2_integration_test.go diff --git a/.env.example b/.env.example index 3de4be47..75a0ae27 100644 --- a/.env.example +++ b/.env.example @@ -319,6 +319,27 @@ TELESRV_STORAGE_RETENTION_MODE=off # check on the shared TELESRV_RETENTION_INTERVAL/TELESRV_RETENTION_BATCH # cadence (Advanced section below). TELESRV_STORAGE_RETENTION_MAX_AGE=720h +# Optional per-category overrides of the shared age above (Photo/Video/Round +# Video/Gif/Music/Voice/File/Avatar) -- each empty/unset value falls back to +# TELESRV_STORAGE_RETENTION_MAX_AGE. The mode switch above still applies to +# all of them; these only let one category expire sooner or later than the +# rest (e.g. purge voice notes after a week but keep files for a year). +TELESRV_STORAGE_RETENTION_MAX_AGE_PHOTO= +TELESRV_STORAGE_RETENTION_MAX_AGE_VIDEO= +TELESRV_STORAGE_RETENTION_MAX_AGE_ROUND_VIDEO= +TELESRV_STORAGE_RETENTION_MAX_AGE_GIF= +TELESRV_STORAGE_RETENTION_MAX_AGE_MUSIC= +TELESRV_STORAGE_RETENTION_MAX_AGE_VOICE= +TELESRV_STORAGE_RETENTION_MAX_AGE_FILE= +TELESRV_STORAGE_RETENTION_MAX_AGE_AVATAR= +# Once enabled, actively reclaims space once total physical storage exceeds +# TELESRV_STORAGE_MAX_TOTAL_BYTES above: the oldest files (regardless of +# category/age) are purged the same way "hard" retention mode purges blob +# bytes, until back under budget. Independent of the retention mode switch +# above -- can run even when that's "off". Default false: TELESRV_STORAGE_MAX_ +# TOTAL_BYTES otherwise only ever blocks new uploads, never reclaims from +# existing ones. +TELESRV_STORAGE_EVICTION_ENABLE=false # ============================================================================== diff --git a/cmd/telesrv-admin/web/dist/assets/index-IZM8WmmY.js b/cmd/telesrv-admin/web/dist/assets/index-By5gK7SA.js similarity index 89% rename from cmd/telesrv-admin/web/dist/assets/index-IZM8WmmY.js rename to cmd/telesrv-admin/web/dist/assets/index-By5gK7SA.js index 44a3f896..65c9a3bf 100644 --- a/cmd/telesrv-admin/web/dist/assets/index-IZM8WmmY.js +++ b/cmd/telesrv-admin/web/dist/assets/index-By5gK7SA.js @@ -6,4 +6,4 @@ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r= `).replace(si,``)}function li(e,t,n){if(t=ci(t),ci(e)!==t&&n)throw Error(r(425))}function ui(){}var di=null,fi=null;function pi(e,t){return e===`textarea`||e===`noscript`||typeof t.children==`string`||typeof t.children==`number`||typeof t.dangerouslySetInnerHTML==`object`&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var mi=typeof setTimeout==`function`?setTimeout:void 0,hi=typeof clearTimeout==`function`?clearTimeout:void 0,gi=typeof Promise==`function`?Promise:void 0,_i=typeof queueMicrotask==`function`?queueMicrotask:gi===void 0?mi:function(e){return gi.resolve(null).then(e).catch(vi)};function vi(e){setTimeout(function(){throw e})}function yi(e,t){var n=t,r=0;do{var i=n.nextSibling;if(e.removeChild(n),i&&i.nodeType===8)if(n=i.data,n===`/$`){if(r===0){e.removeChild(i),tn(t);return}r--}else n!==`$`&&n!==`$?`&&n!==`$!`||r++;n=i}while(n);tn(t)}function bi(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t===`$`||t===`$!`||t===`$?`)break;if(t===`/$`)return null}}return e}function xi(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var n=e.data;if(n===`$`||n===`$!`||n===`$?`){if(t===0)return e;t--}else n===`/$`&&t++}e=e.previousSibling}return null}var Si=Math.random().toString(36).slice(2),Ci=`__reactFiber$`+Si,wi=`__reactProps$`+Si,Ti=`__reactContainer$`+Si,Ei=`__reactEvents$`+Si,Di=`__reactListeners$`+Si,Oi=`__reactHandles$`+Si;function ki(e){var t=e[Ci];if(t)return t;for(var n=e.parentNode;n;){if(t=n[Ti]||n[Ci]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=xi(e);e!==null;){if(n=e[Ci])return n;e=xi(e)}return t}e=n,n=e.parentNode}return null}function Ai(e){return e=e[Ci]||e[Ti],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function ji(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(r(33))}function Mi(e){return e[wi]||null}var Ni=[],Pi=-1;function Fi(e){return{current:e}}function Ii(e){0>Pi||(e.current=Ni[Pi],Ni[Pi]=null,Pi--)}function Li(e,t){Pi++,Ni[Pi]=e.current,e.current=t}var Ri={},zi=Fi(Ri),Bi=Fi(!1),Vi=Ri;function Hi(e,t){var n=e.type.contextTypes;if(!n)return Ri;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var i={},a;for(a in n)i[a]=t[a];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=i),i}function Ui(e){return e=e.childContextTypes,e!=null}function Wi(){Ii(Bi),Ii(zi)}function Gi(e,t,n){if(zi.current!==Ri)throw Error(r(168));Li(zi,t),Li(Bi,n)}function Ki(e,t,n){var i=e.stateNode;if(t=t.childContextTypes,typeof i.getChildContext!=`function`)return n;for(var a in i=i.getChildContext(),i)if(!(a in t))throw Error(r(108,le(e)||`Unknown`,a));return re({},n,i)}function qi(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Ri,Vi=zi.current,Li(zi,e),Li(Bi,Bi.current),!0}function Ji(e,t,n){var i=e.stateNode;if(!i)throw Error(r(169));n?(e=Ki(e,t,Vi),i.__reactInternalMemoizedMergedChildContext=e,Ii(Bi),Ii(zi),Li(zi,e)):Ii(Bi),Li(Bi,n)}var Yi=null,Xi=!1,Zi=!1;function Qi(e){Yi===null?Yi=[e]:Yi.push(e)}function $i(e){Xi=!0,Qi(e)}function ea(){if(!Zi&&Yi!==null){Zi=!0;var e=0,t=G;try{var n=Yi;for(G=1;e>=o,i-=o,ca=1<<32-Ct(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(r,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(r,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(r,d),ga&&ua(r,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),ga&&ua(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return ga&&ua(a,g),u}for(h=i(a,h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),ga&&ua(a,g),u}function _(e,r,i,o){if(typeof i==`object`&&i&&i.type===E&&i.key===null&&(i=i.props.children),typeof i==`object`&&i){switch(i.$$typeof){case w:a:{for(var c=i.key,l=r;l!==null;){if(l.key===c){if(c=i.type,c===E){if(l.tag===7){n(e,l.sibling),r=a(l,i.props.children),r.return=e,e=r;break a}}else if(l.elementType===c||typeof c==`object`&&c&&c.$$typeof===ee&&Aa(c)===l.type){n(e,l.sibling),r=a(l,i.props),r.ref=Oa(e,l,i),r.return=e,e=r;break a}n(e,l);break}else t(e,l);l=l.sibling}i.type===E?(r=Zl(i.props.children,e.mode,o,i.key),r.return=e,e=r):(o=Xl(i.type,i.key,i.props,null,e.mode,o),o.ref=Oa(e,r,i),o.return=e,e=o)}return s(e);case T:a:{for(l=i.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===i.containerInfo&&r.stateNode.implementation===i.implementation){n(e,r.sibling),r=a(r,i.children||[]),r.return=e,e=r;break a}else{n(e,r);break}else t(e,r);r=r.sibling}r=eu(i,e.mode,o),r.return=e,e=r}return s(e);case ee:return l=i._init,_(e,r,l(i._payload),o)}if(xe(i))return h(e,r,i,o);if(ne(i))return g(e,r,i,o);ka(e,i)}return typeof i==`string`&&i!==``||typeof i==`number`?(i=``+i,r!==null&&r.tag===6?(n(e,r.sibling),r=a(r,i),r.return=e,e=r):(n(e,r),r=$l(i,e.mode,o),r.return=e,e=r),s(e)):n(e,r)}return _}var Ma=ja(!0),Na=ja(!1),Pa=Fi(null),Fa=null,Ia=null,La=null;function Ra(){La=Ia=Fa=null}function za(e){var t=Pa.current;Ii(Pa),e._currentValue=t}function Ba(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)===t?r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t):(e.childLanes|=t,r!==null&&(r.childLanes|=t)),e===n)break;e=e.return}}function Va(e,t){Fa=e,La=Ia=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&t)!==0&&(js=!0),e.firstContext=null)}function Ha(e){var t=e._currentValue;if(La!==e)if(e={context:e,memoizedValue:t,next:null},Ia===null){if(Fa===null)throw Error(r(308));Ia=e,Fa.dependencies={lanes:0,firstContext:e}}else Ia=Ia.next=e;return t}var Ua=null;function Wa(e){Ua===null?Ua=[e]:Ua.push(e)}function Ga(e,t,n,r){var i=t.interleaved;return i===null?(n.next=n,Wa(t)):(n.next=i.next,i.next=n),t.interleaved=n,Ka(e,r)}function Ka(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var qa=!1;function Ja(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Ya(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Xa(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Za(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,Bc&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,Ka(e,n)}return i=r.interleaved,i===null?(t.next=t,Wa(r)):(t.next=i.next,i.next=t),r.interleaved=t,Ka(e,n)}function Qa(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194240)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,W(e,n)}}function $a(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function eo(e,t,n,r){var i=e.updateQueue;qa=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane,p=s.eventTime;if((r&f)===f){u!==null&&(u=u.next={eventTime:p,lane:0,tag:s.tag,payload:s.payload,callback:s.callback,next:null});a:{var m=e,h=s;switch(f=t,p=n,h.tag){case 1:if(m=h.payload,typeof m==`function`){d=m.call(p,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=h.payload,f=typeof m==`function`?m.call(p,d,f):m,f==null)break a;d=re({},d,f);break a;case 2:qa=!0}}s.callback!==null&&s.lane!==0&&(e.flags|=64,f=i.effects,f===null?i.effects=[s]:f.push(s))}else p={eventTime:p,lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;f=s,s=f.next,f.next=null,i.lastBaseUpdate=f,i.shared.pending=null}}while(1);if(u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,t=i.shared.interleaved,t!==null){i=t;do o|=i.lane,i=i.next;while(i!==t)}else a===null&&(i.shared.lanes=0);Jc|=o,e.lanes=o,e.memoizedState=d}}function to(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=_o.transition;_o.transition={};try{e(!1),t()}finally{G=n,_o.transition=r}}function is(){return jo().memoizedState}function as(e,t,n){var r=pl(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},ss(e))cs(t,n);else if(n=Ga(e,t,n,r),n!==null){var i=fl();ml(n,e,r,i),ls(n,t,r)}}function os(e,t,n){var r=pl(e),i={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(ss(e))cs(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Q(s,o)){var c=t.interleaved;c===null?(i.next=i,Wa(t)):(i.next=c.next,c.next=i),t.interleaved=i;return}}catch{}n=Ga(e,t,i,r),n!==null&&(i=fl(),ml(n,e,r,i),ls(n,t,r))}}function ss(e){var t=e.alternate;return e===yo||t!==null&&t===yo}function cs(e,t){Co=So=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function ls(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,W(e,n)}}var us={readContext:Ha,useCallback:Eo,useContext:Eo,useEffect:Eo,useImperativeHandle:Eo,useInsertionEffect:Eo,useLayoutEffect:Eo,useMemo:Eo,useReducer:Eo,useRef:Eo,useState:Eo,useDebugValue:Eo,useDeferredValue:Eo,useTransition:Eo,useMutableSource:Eo,useSyncExternalStore:Eo,useId:Eo,unstable_isNewReconciler:!1},ds={readContext:Ha,useCallback:function(e,t){return Ao().memoizedState=[e,t===void 0?null:t],e},useContext:Ha,useEffect:qo,useImperativeHandle:function(e,t,n){return n=n==null?null:n.concat([e]),Go(4194308,4,Zo.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Go(4194308,4,e,t)},useInsertionEffect:function(e,t){return Go(4,2,e,t)},useMemo:function(e,t){var n=Ao();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Ao();return t=n===void 0?t:n(t),r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=as.bind(null,yo,e),[r.memoizedState,e]},useRef:function(e){var t=Ao();return e={current:e},t.memoizedState=e},useState:Ho,useDebugValue:$o,useDeferredValue:function(e){return Ao().memoizedState=e},useTransition:function(){var e=Ho(!1),t=e[0];return e=rs.bind(null,e[1]),Ao().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var i=yo,a=Ao();if(ga){if(n===void 0)throw Error(r(407));n=n()}else{if(n=t(),Vc===null)throw Error(r(349));vo&30||Lo(i,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,qo(zo.bind(null,i,o,e),[e]),i.flags|=2048,Uo(9,Ro.bind(null,i,o,n,t),void 0,null),n},useId:function(){var e=Ao(),t=Vc.identifierPrefix;if(ga){var n=la,r=ca;n=(r&~(1<<32-Ct(r)-1)).toString(32)+n,t=`:`+t+`R`+n,n=wo++,0<\/script>`,e=e.removeChild(e.firstChild)):typeof i.is==`string`?e=c.createElement(n,{is:i.is}):(e=c.createElement(n),n===`select`&&(c=e,i.multiple?c.multiple=!0:i.size&&(c.size=i.size))):e=c.createElementNS(e,n),e[Ci]=t,e[wi]=i,tc(e,t,!1,!1),t.stateNode=e;a:{switch(c=Le(n,i),n){case`dialog`:Xr(`cancel`,e),Xr(`close`,e),o=i;break;case`iframe`:case`object`:case`embed`:Xr(`load`,e),o=i;break;case`video`:case`audio`:for(o=0;oel&&(t.flags|=128,i=!0,ic(s,!1),t.lanes=4194304)}else{if(!i)if(e=po(c),e!==null){if(t.flags|=128,i=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),ic(s,!0),s.tail===null&&s.tailMode===`hidden`&&!c.alternate&&!ga)return ac(t),null}else 2*mt()-s.renderingStartTime>el&&n!==1073741824&&(t.flags|=128,i=!0,ic(s,!1),t.lanes=4194304);s.isBackwards?(c.sibling=t.child,t.child=c):(n=s.last,n===null?t.child=c:n.sibling=c,s.last=c)}return s.tail===null?(ac(t),null):(t=s.tail,s.rendering=t,s.tail=t.sibling,s.renderingStartTime=mt(),t.sibling=null,n=fo.current,Li(fo,i?n&1|2:n&1),t);case 22:case 23:return wl(),i=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==i&&(t.flags|=8192),i&&t.mode&1?Wc&1073741824&&(ac(t),t.subtreeFlags&6&&(t.flags|=8192)):ac(t),null;case 24:return null;case 25:return null}throw Error(r(156,t.tag))}function sc(e,t){switch(pa(t),t.tag){case 1:return Ui(t.type)&&Wi(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return co(),Ii(Bi),Ii(zi),ho(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return uo(t),null;case 13:if(Ii(fo),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(r(340));Ta()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Ii(fo),null;case 4:return co(),null;case 10:return za(t.type._context),null;case 22:case 23:return wl(),null;case 24:return null;default:return null}}var cc=!1,lc=!1,uc=typeof WeakSet==`function`?WeakSet:Set,$=null;function dc(e,t){var n=e.ref;if(n!==null)if(typeof n==`function`)try{n(null)}catch(n){Rl(e,t,n)}else n.current=null}function fc(e,t,n){try{n()}catch(n){Rl(e,t,n)}}var pc=!1;function mc(e,t){if(di=rn,e=Cr(),wr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var i=n.getSelection&&n.getSelection();if(i&&i.rangeCount!==0){n=i.anchorNode;var a=i.anchorOffset,o=i.focusNode;i=i.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||i!==0&&f.nodeType!==3||(l=s+i),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===i&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(fi={focusedElem:e,selectionRange:n},rn=!1,$=t;$!==null;)if(t=$,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,$=e;else for(;$!==null;){t=$;try{var h=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(h!==null){var g=h.memoizedProps,_=h.memoizedState,v=t.stateNode;v.__reactInternalSnapshotBeforeUpdate=v.getSnapshotBeforeUpdate(t.elementType===t.type?g:ms(t.type,g),_)}break;case 3:var y=t.stateNode.containerInfo;y.nodeType===1?y.textContent=``:y.nodeType===9&&y.documentElement&&y.removeChild(y.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(r(163))}}catch(e){Rl(t,t.return,e)}if(e=t.sibling,e!==null){e.return=t.return,$=e;break}$=t.return}return h=pc,pc=!1,h}function hc(e,t,n){var r=t.updateQueue;if(r=r===null?null:r.lastEffect,r!==null){var i=r=r.next;do{if((i.tag&e)===e){var a=i.destroy;i.destroy=void 0,a!==void 0&&fc(t,n,a)}i=i.next}while(i!==r)}}function gc(e,t){if(t=t.updateQueue,t=t===null?null:t.lastEffect,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function _c(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t==`function`?t(e):t.current=e}}function vc(e){var t=e.alternate;t!==null&&(e.alternate=null,vc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ci],delete t[wi],delete t[Ei],delete t[Di],delete t[Oi])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function yc(e){return e.tag===5||e.tag===3||e.tag===4}function bc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||yc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function xc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ui));else if(r!==4&&(e=e.child,e!==null))for(xc(e,t,n),e=e.sibling;e!==null;)xc(e,t,n),e=e.sibling}function Sc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Sc(e,t,n),e=e.sibling;e!==null;)Sc(e,t,n),e=e.sibling}var Cc=null,wc=!1;function Tc(e,t,n){for(n=n.child;n!==null;)Ec(e,t,n),n=n.sibling}function Ec(e,t,n){if(H&&typeof H.onCommitFiberUnmount==`function`)try{H.onCommitFiberUnmount(xt,n)}catch{}switch(n.tag){case 5:lc||dc(n,t);case 6:var r=Cc,i=wc;Cc=null,Tc(e,t,n),Cc=r,wc=i,Cc!==null&&(wc?(e=Cc,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Cc.removeChild(n.stateNode));break;case 18:Cc!==null&&(wc?(e=Cc,n=n.stateNode,e.nodeType===8?yi(e.parentNode,n):e.nodeType===1&&yi(e,n),tn(e)):yi(Cc,n.stateNode));break;case 4:r=Cc,i=wc,Cc=n.stateNode.containerInfo,wc=!0,Tc(e,t,n),Cc=r,wc=i;break;case 0:case 11:case 14:case 15:if(!lc&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){i=r=r.next;do{var a=i,o=a.destroy;a=a.tag,o!==void 0&&(a&2||a&4)&&fc(n,t,o),i=i.next}while(i!==r)}Tc(e,t,n);break;case 1:if(!lc&&(dc(n,t),r=n.stateNode,typeof r.componentWillUnmount==`function`))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(e){Rl(n,t,e)}Tc(e,t,n);break;case 21:Tc(e,t,n);break;case 22:n.mode&1?(lc=(r=lc)||n.memoizedState!==null,Tc(e,t,n),lc=r):Tc(e,t,n);break;default:Tc(e,t,n)}}function Dc(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new uc),t.forEach(function(t){var r=Hl.bind(null,e,t);n.has(t)||(n.add(t),t.then(r,r))})}}function Oc(e,t){var n=t.deletions;if(n!==null)for(var i=0;ia&&(a=s),i&=~o}if(i=a,i=mt()-i,i=(120>i?120:480>i?480:1080>i?1080:1920>i?1920:3e3>i?3e3:4320>i?4320:1960*Ic(i/1960))-i,10e?16:e,ol===null)var i=!1;else{if(e=ol,ol=null,sl=0,Bc&6)throw Error(r(331));var a=Bc;for(Bc|=4,$=e.current;$!==null;){var o=$,s=o.child;if($.flags&16){var c=o.deletions;if(c!==null){for(var l=0;lmt()-$c?Tl(e,0):Xc|=n),hl(e,t)}function Bl(e,t){t===0&&(e.mode&1?(t=Dt,Dt<<=1,!(Dt&130023424)&&(Dt=4194304)):t=1);var n=fl();e=Ka(e,t),e!==null&&(Ft(e,t,n),hl(e,n))}function Vl(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Bl(e,n)}function Hl(e,t){var n=0;switch(e.tag){case 13:var i=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:i=e.stateNode;break;default:throw Error(r(314))}i!==null&&i.delete(t),Bl(e,n)}var Ul=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Bi.current)js=!0;else{if((e.lanes&n)===0&&!(t.flags&128))return js=!1,ec(e,t,n);js=!!(e.flags&131072)}else js=!1,ga&&t.flags&1048576&&da(t,ia,t.index);switch(t.lanes=0,t.tag){case 2:var i=t.type;Qs(e,t),e=t.pendingProps;var a=Hi(t,zi.current);Va(t,n),a=Oo(null,t,i,e,a,n);var o=ko();return t.flags|=1,typeof a==`object`&&a&&typeof a.render==`function`&&a.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Ui(i)?(o=!0,qi(t)):o=!1,t.memoizedState=a.state!==null&&a.state!==void 0?a.state:null,Ja(t),a.updater=gs,t.stateNode=a,a._reactInternals=t,bs(t,i,e,n),t=Bs(null,t,i,!0,o,n)):(t.tag=0,ga&&o&&fa(t),Ms(null,t,a,n),t=t.child),t;case 16:i=t.elementType;a:{switch(Qs(e,t),e=t.pendingProps,a=i._init,i=a(i._payload),t.type=i,a=t.tag=Jl(i),e=ms(i,e),a){case 0:t=Rs(null,t,i,e,n);break a;case 1:t=zs(null,t,i,e,n);break a;case 11:t=Ns(null,t,i,e,n);break a;case 14:t=Ps(null,t,i,ms(i.type,e),n);break a}throw Error(r(306,i,``))}return t;case 0:return i=t.type,a=t.pendingProps,a=t.elementType===i?a:ms(i,a),Rs(e,t,i,a,n);case 1:return i=t.type,a=t.pendingProps,a=t.elementType===i?a:ms(i,a),zs(e,t,i,a,n);case 3:a:{if(Vs(t),e===null)throw Error(r(387));i=t.pendingProps,o=t.memoizedState,a=o.element,Ya(e,t),eo(t,i,null,n);var s=t.memoizedState;if(i=s.element,o.isDehydrated)if(o={element:i,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){a=xs(Error(r(423)),t),t=Hs(e,t,i,n,a);break a}else if(i!==a){a=xs(Error(r(424)),t),t=Hs(e,t,i,n,a);break a}else for(ha=bi(t.stateNode.containerInfo.firstChild),ma=t,ga=!0,_a=null,n=Na(t,null,i,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Ta(),i===a){t=$s(e,t,n);break a}Ms(e,t,i,n)}t=t.child}return t;case 5:return lo(t),e===null&&xa(t),i=t.type,a=t.pendingProps,o=e===null?null:e.memoizedProps,s=a.children,pi(i,a)?s=null:o!==null&&pi(i,o)&&(t.flags|=32),Ls(e,t),Ms(e,t,s,n),t.child;case 6:return e===null&&xa(t),null;case 13:return Gs(e,t,n);case 4:return so(t,t.stateNode.containerInfo),i=t.pendingProps,e===null?t.child=Ma(t,null,i,n):Ms(e,t,i,n),t.child;case 11:return i=t.type,a=t.pendingProps,a=t.elementType===i?a:ms(i,a),Ns(e,t,i,a,n);case 7:return Ms(e,t,t.pendingProps,n),t.child;case 8:return Ms(e,t,t.pendingProps.children,n),t.child;case 12:return Ms(e,t,t.pendingProps.children,n),t.child;case 10:a:{if(i=t.type._context,a=t.pendingProps,o=t.memoizedProps,s=a.value,Li(Pa,i._currentValue),i._currentValue=s,o!==null)if(Q(o.value,s)){if(o.children===a.children&&!Bi.current){t=$s(e,t,n);break a}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var c=o.dependencies;if(c!==null){s=o.child;for(var l=c.firstContext;l!==null;){if(l.context===i){if(o.tag===1){l=Xa(-1,n&-n),l.tag=2;var u=o.updateQueue;if(u!==null){u=u.shared;var d=u.pending;d===null?l.next=l:(l.next=d.next,d.next=l),u.pending=l}}o.lanes|=n,l=o.alternate,l!==null&&(l.lanes|=n),Ba(o.return,n,t),c.lanes|=n;break}l=l.next}}else if(o.tag===10)s=o.type===t.type?null:o.child;else if(o.tag===18){if(s=o.return,s===null)throw Error(r(341));s.lanes|=n,c=s.alternate,c!==null&&(c.lanes|=n),Ba(s,n,t),s=o.sibling}else s=o.child;if(s!==null)s.return=o;else for(s=o;s!==null;){if(s===t){s=null;break}if(o=s.sibling,o!==null){o.return=s.return,s=o;break}s=s.return}o=s}Ms(e,t,a.children,n),t=t.child}return t;case 9:return a=t.type,i=t.pendingProps.children,Va(t,n),a=Ha(a),i=i(a),t.flags|=1,Ms(e,t,i,n),t.child;case 14:return i=t.type,a=ms(i,t.pendingProps),a=ms(i.type,a),Ps(e,t,i,a,n);case 15:return Fs(e,t,t.type,t.pendingProps,n);case 17:return i=t.type,a=t.pendingProps,a=t.elementType===i?a:ms(i,a),Qs(e,t),t.tag=1,Ui(i)?(e=!0,qi(t)):e=!1,Va(t,n),vs(t,i,a),bs(t,i,a,n),Bs(null,t,i,!0,e,n);case 19:return Zs(e,t,n);case 22:return Is(e,t,n)}throw Error(r(156,t.tag))};function Wl(e,t){return ut(e,t)}function Gl(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Kl(e,t,n,r){return new Gl(e,t,n,r)}function ql(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Jl(e){if(typeof e==`function`)return+!!ql(e);if(e!=null){if(e=e.$$typeof,e===j)return 11;if(e===P)return 14}return 2}function Yl(e,t){var n=e.alternate;return n===null?(n=Kl(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Xl(e,t,n,i,a,o){var s=2;if(i=e,typeof e==`function`)ql(e)&&(s=1);else if(typeof e==`string`)s=5;else a:switch(e){case E:return Zl(n.children,a,o,t);case D:s=8,a|=8;break;case O:return e=Kl(12,n,t,a|2),e.elementType=O,e.lanes=o,e;case M:return e=Kl(13,n,t,a),e.elementType=M,e.lanes=o,e;case N:return e=Kl(19,n,t,a),e.elementType=N,e.lanes=o,e;case te:return Ql(n,a,o,t);default:if(typeof e==`object`&&e)switch(e.$$typeof){case k:s=10;break a;case A:s=9;break a;case j:s=11;break a;case P:s=14;break a;case ee:s=16,i=null;break a}throw Error(r(130,e==null?e:typeof e,``))}return t=Kl(s,n,t,a),t.elementType=e,t.type=i,t.lanes=o,t}function Zl(e,t,n,r){return e=Kl(7,e,r,t),e.lanes=n,e}function Ql(e,t,n,r){return e=Kl(22,e,r,t),e.elementType=te,e.lanes=n,e.stateNode={isHidden:!1},e}function $l(e,t,n){return e=Kl(6,e,null,t),e.lanes=n,e}function eu(e,t,n){return t=Kl(4,e.children===null?[]:e.children,e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function tu(e,t,n,r,i){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Pt(0),this.expirationTimes=Pt(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Pt(0),this.identifierPrefix=r,this.onRecoverableError=i,this.mutableSourceEagerHydrationData=null}function nu(e,t,n,r,i,a,o,s,c){return e=new tu(e,t,n,s,c),t===1?(t=1,!0===a&&(t|=8)):t=0,a=Kl(3,null,null,t),e.current=a,a.stateNode=e,a.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ja(a),e}function ru(e,t,n){var r=3{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=p()})),h=o((e=>{var t=m();e.createRoot=t.createRoot,e.hydrateRoot=t.hydrateRoot})),g=c(u()),_=c(h(),1),v=class extends Error{status;constructor(e,t){super(t),this.status=e}},y=`telesrv_admin_csrf`,b=`X-CSRF-Token`,x=``;function S(e){x=(e??``).trim()}function C(){if(typeof document>`u`)return``;for(let e of document.cookie.split(`;`)){let t=e.trim(),n=t.indexOf(`=`);if(!(n<=0||t.slice(0,n)!==y))try{return decodeURIComponent(t.slice(n+1))}catch{return t.slice(n+1)}}return``}function w(){return C()||x}function T(e){let t=(e??`GET`).toUpperCase();return t!==`GET`&&t!==`HEAD`&&t!==`OPTIONS`}function E(e){if(!e)return{};if(e instanceof Headers){let t={};return e.forEach((e,n)=>{t[n]=e}),t}return Array.isArray(e)?Object.fromEntries(e):{...e}}async function D(e,t={}){let n=typeof FormData<`u`&&t.body instanceof FormData?{}:{"Content-Type":`application/json`};if(Object.assign(n,E(t.headers)),T(t.method)){let e=w();e&&(n[b]=e)}let r=await fetch(e,{credentials:`same-origin`,...t,headers:n}),i=await r.text(),a=i?JSON.parse(i):null;if(!r.ok){let e=a?.error||a?.Error||a?.message||r.statusText;throw new v(r.status,e)}return a}function O(e){return e instanceof Error?e.message:String(e)}var k={session:()=>D(`/api/session`),login:async e=>{let t=await D(`/api/login`,{method:`POST`,body:JSON.stringify({secret:e})});return S(t.csrf_token),t},logout:()=>D(`/api/logout`,{method:`POST`,body:`{}`}),accounts:e=>D(`/api/accounts?${e.toString()}`),accountStats:()=>D(`/api/accounts/stats`),sharedDeviceGroups:e=>D(`/api/accounts/shared-devices?${e.toString()}`),account:e=>D(`/api/accounts/${e}`),channels:e=>D(`/api/channels?${e.toString()}`),channel:e=>D(`/api/channels/${e}`),bots:e=>D(`/api/bots?${e.toString()}`),broadcasts:e=>D(`/api/broadcasts?${e.toString()}`),bot:e=>D(`/api/bots/${e}`),collectibleUsernames:e=>D(`/api/collectible-usernames?${e.toString()}`),collectibleUsername:e=>D(`/api/collectible-usernames/${encodeURIComponent(e)}`),dashboard:()=>D(`/api/dashboard`),storageStats:()=>D(`/api/storage/stats`),storageAccounts:e=>D(`/api/storage/accounts?${e.toString()}`),verificationApplications:e=>D(`/api/verification/applications?${e.toString()}`),verificationApplication:e=>D(`/api/verification/applications/${encodeURIComponent(e)}`),verificationCounts:()=>D(`/api/verification/counts`),botVerifiers:e=>D(`/api/botverification/verifiers?${e.toString()}`),verificationIcons:e=>D(`/api/botverification/icons?${e.toString()}`),customVerifications:e=>D(`/api/botverification/marks?${e.toString()}`),customVerificationRequests:e=>D(`/api/botverification/requests?${e.toString()}`),customVerificationRequest:e=>D(`/api/botverification/requests/${encodeURIComponent(e)}`),botVerificationCounts:()=>D(`/api/botverification/counts`),emoji:e=>D(`/api/emoji?${e.toString()}`),emojiAnimation:e=>D(`/api/emoji/${encodeURIComponent(e)}/animation`),messages:e=>D(`/api/messages?${e.toString()}`),message:(e,t)=>D(`/api/messages/detail?${new URLSearchParams({owner_user_id:String(e),msg_id:String(t)}).toString()}`),groupMessages:e=>D(`/api/messages/groups?${e.toString()}`),groupMessage:(e,t)=>D(`/api/messages/groups/detail?${new URLSearchParams({channel_id:String(e),msg_id:String(t)}).toString()}`),moderationCases:e=>D(`/api/moderation/cases?${e.toString()}`),moderationCase:e=>D(`/api/moderation/cases/${e}`),moderationReport:e=>D(`/api/moderation/reports/${e}`),claimModerationCase:(e,t)=>D(`/api/moderation/cases/${e}/claim`,{method:`POST`,body:JSON.stringify({expected_version:t})}),decideModerationCase:(e,t)=>D(`/api/moderation/cases/${e}/decide`,{method:`POST`,body:JSON.stringify(t)}),reviewModerationAppeal:(e,t,n)=>D(`/api/moderation/cases/${e}/appeals/${t}/review`,{method:`POST`,body:JSON.stringify(n)}),stickerSets:e=>D(`/api/stickers?kind=${encodeURIComponent(e)}`),stickerSetDocuments:e=>D(`/api/stickers/${encodeURIComponent(e)}/documents`),stickerDocumentAnimationURL:e=>`/api/stickers/documents/${encodeURIComponent(e)}/animation`,gifCatalogDocumentPreviewURL:e=>`/api/gif-catalog/documents/${encodeURIComponent(e)}/preview`,createStickerSet:e=>D(`/api/actions/create-sticker-set`,{method:`POST`,body:e}),setAccountAvatar:e=>D(`/api/actions/set-account-avatar`,{method:`POST`,body:e}),setChannelAvatar:e=>D(`/api/actions/set-channel-avatar`,{method:`POST`,body:e}),addStickerToSet:e=>D(`/api/actions/add-sticker-to-set`,{method:`POST`,body:e}),gifCatalog:()=>D(`/api/gif-catalog`),createGifCatalogEntry:e=>D(`/api/actions/create-gif-catalog-entry`,{method:`POST`,body:e}),serverIdentity:()=>D(`/api/server/identity`),addServerLink:()=>D(`/api/server/add-server-link`),uploadServerIcon:e=>D(`/api/actions/upload-server-icon`,{method:`POST`,body:e}),serverIconURL:()=>`/api/server/icon?t=${Date.now()}`,serverEnv:()=>D(`/api/server/env`),serverStatus:()=>D(`/api/server/status`),dockerStatus:()=>D(`/api/server/docker-status`),checkServerUpdates:()=>D(`/api/server/check-updates`),action:(e,t)=>D(e,{method:`POST`,body:JSON.stringify(t)})},A=e=>e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase(),j=(...e)=>e.filter((e,t,n)=>!!e&&e.trim()!==``&&n.indexOf(e)===t).join(` `).trim(),M={xmlns:`http://www.w3.org/2000/svg`,width:24,height:24,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:2,strokeLinecap:`round`,strokeLinejoin:`round`},N=(0,g.forwardRef)(({color:e=`currentColor`,size:t=24,strokeWidth:n=2,absoluteStrokeWidth:r,className:i=``,children:a,iconNode:o,...s},c)=>(0,g.createElement)(`svg`,{ref:c,...M,width:t,height:t,stroke:e,strokeWidth:r?Number(n)*24/Number(t):n,className:j(`lucide`,i),...s},[...o.map(([e,t])=>(0,g.createElement)(e,t)),...Array.isArray(a)?a:[a]])),P=(e,t)=>{let n=(0,g.forwardRef)(({className:n,...r},i)=>(0,g.createElement)(N,{ref:i,iconNode:t,className:j(`lucide-${A(e)}`,n),...r}));return n.displayName=`${e}`,n},ee=P(`BadgeCheck`,[[`path`,{d:`M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z`,key:`3c2336`}],[`path`,{d:`m9 12 2 2 4-4`,key:`dzmm74`}]]),te=P(`CircleAlert`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`line`,{x1:`12`,x2:`12`,y1:`8`,y2:`12`,key:`1pkeuh`}],[`line`,{x1:`12`,x2:`12.01`,y1:`16`,y2:`16`,key:`4dfq90`}]]),F=P(`CircleCheck`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m9 12 2 2 4-4`,key:`dzmm74`}]]),ne=P(`CircleX`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]]),re=P(`Layers`,[[`path`,{d:`M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z`,key:`zw3jo`}],[`path`,{d:`M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12`,key:`1wduqc`}],[`path`,{d:`M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17`,key:`kqbvx6`}]]),I=P(`LoaderCircle`,[[`path`,{d:`M21 12a9 9 0 1 1-6.219-8.56`,key:`13zald`}]]),ie=P(`ShieldX`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`m14.5 9.5-5 5`,key:`17q4r4`}],[`path`,{d:`m9.5 9.5 5 5`,key:`18nt4w`}]]),ae=P(`Sparkles`,[[`path`,{d:`M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z`,key:`4pj2yx`}],[`path`,{d:`M20 3v4`,key:`1olli1`}],[`path`,{d:`M22 5h-4`,key:`1gvqau`}],[`path`,{d:`M4 17v2`,key:`vumght`}],[`path`,{d:`M5 18H3`,key:`zchphs`}]]),oe=P(`TriangleAlert`,[[`path`,{d:`m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3`,key:`wmoenq`}],[`path`,{d:`M12 9v4`,key:`juzpu7`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]]),se=P(`UserRound`,[[`circle`,{cx:`12`,cy:`8`,r:`5`,key:`1hypcn`}],[`path`,{d:`M20 21a8 8 0 0 0-16 0`,key:`rfgkzh`}]]),ce=P(`UsersRound`,[[`path`,{d:`M18 21a8 8 0 0 0-16 0`,key:`3ypg7q`}],[`circle`,{cx:`10`,cy:`8`,r:`5`,key:`o932ke`}],[`path`,{d:`M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3`,key:`10s06x`}]]),le=P(`Activity`,[[`path`,{d:`M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2`,key:`169zse`}]]),ue=P(`ArrowDown`,[[`path`,{d:`M12 5v14`,key:`s699le`}],[`path`,{d:`m19 12-7 7-7-7`,key:`1idqje`}]]),de=P(`ArrowLeftRight`,[[`path`,{d:`M8 3 4 7l4 4`,key:`9rb6wj`}],[`path`,{d:`M4 7h16`,key:`6tx8e3`}],[`path`,{d:`m16 21 4-4-4-4`,key:`siv7j2`}],[`path`,{d:`M20 17H4`,key:`h6l3hr`}]]),fe=P(`ArrowLeft`,[[`path`,{d:`m12 19-7-7 7-7`,key:`1l729n`}],[`path`,{d:`M19 12H5`,key:`x3x0zl`}]]),L=P(`ArrowUpDown`,[[`path`,{d:`m21 16-4 4-4-4`,key:`f6ql7i`}],[`path`,{d:`M17 20V4`,key:`1ejh1v`}],[`path`,{d:`m3 8 4-4 4 4`,key:`11wl7u`}],[`path`,{d:`M7 4v16`,key:`1glfcx`}]]),pe=P(`ArrowUp`,[[`path`,{d:`m5 12 7-7 7 7`,key:`hav0vg`}],[`path`,{d:`M12 19V5`,key:`x0mq9r`}]]),me=P(`AtSign`,[[`circle`,{cx:`12`,cy:`12`,r:`4`,key:`4exip2`}],[`path`,{d:`M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8`,key:`7n84p3`}]]),he=P(`Ban`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m4.9 4.9 14.2 14.2`,key:`1m5liu`}]]),ge=P(`Bot`,[[`path`,{d:`M12 8V4H8`,key:`hb8ula`}],[`rect`,{width:`16`,height:`12`,x:`4`,y:`8`,rx:`2`,key:`enze0r`}],[`path`,{d:`M2 14h2`,key:`vft8re`}],[`path`,{d:`M20 14h2`,key:`4cs60a`}],[`path`,{d:`M15 13v2`,key:`1xurst`}],[`path`,{d:`M9 13v2`,key:`rq6x2g`}]]),_e=P(`Building2`,[[`path`,{d:`M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z`,key:`1b4qmf`}],[`path`,{d:`M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2`,key:`i71pzd`}],[`path`,{d:`M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2`,key:`10jefs`}],[`path`,{d:`M10 6h4`,key:`1itunk`}],[`path`,{d:`M10 10h4`,key:`tcdvrf`}],[`path`,{d:`M10 14h4`,key:`kelpxr`}],[`path`,{d:`M10 18h4`,key:`1ulq68`}]]),ve=P(`Cable`,[[`path`,{d:`M17 21v-2a1 1 0 0 1-1-1v-1a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1`,key:`10bnsj`}],[`path`,{d:`M19 15V6.5a1 1 0 0 0-7 0v11a1 1 0 0 1-7 0V9`,key:`1eqmu1`}],[`path`,{d:`M21 21v-2h-4`,key:`14zm7j`}],[`path`,{d:`M3 5h4V3`,key:`z442eg`}],[`path`,{d:`M7 5a1 1 0 0 1 1 1v1a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a1 1 0 0 1 1-1V3`,key:`ebdjd7`}]]),ye=P(`Check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),be=P(`ChevronDown`,[[`path`,{d:`m6 9 6 6 6-6`,key:`qrunsl`}]]),xe=P(`ChevronLeft`,[[`path`,{d:`m15 18-6-6 6-6`,key:`1wnfg3`}]]),Se=P(`ChevronRight`,[[`path`,{d:`m9 18 6-6-6-6`,key:`mthhwq`}]]),Ce=P(`CircleOff`,[[`path`,{d:`m2 2 20 20`,key:`1ooewy`}],[`path`,{d:`M8.35 2.69A10 10 0 0 1 21.3 15.65`,key:`1pfsoa`}],[`path`,{d:`M19.08 19.08A10 10 0 1 1 4.92 4.92`,key:`1ablyi`}]]),R=P(`Copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),we=P(`Cpu`,[[`rect`,{width:`16`,height:`16`,x:`4`,y:`4`,rx:`2`,key:`14l7u7`}],[`rect`,{width:`6`,height:`6`,x:`9`,y:`9`,rx:`1`,key:`5aljv4`}],[`path`,{d:`M15 2v2`,key:`13l42r`}],[`path`,{d:`M15 20v2`,key:`15mkzm`}],[`path`,{d:`M2 15h2`,key:`1gxd5l`}],[`path`,{d:`M2 9h2`,key:`1bbxkp`}],[`path`,{d:`M20 15h2`,key:`19e6y8`}],[`path`,{d:`M20 9h2`,key:`19tzq7`}],[`path`,{d:`M9 2v2`,key:`165o2o`}],[`path`,{d:`M9 20v2`,key:`i2bqo8`}]]),Te=P(`Database`,[[`ellipse`,{cx:`12`,cy:`5`,rx:`9`,ry:`3`,key:`msslwz`}],[`path`,{d:`M3 5V19A9 3 0 0 0 21 19V5`,key:`1wlel7`}],[`path`,{d:`M3 12A9 3 0 0 0 21 12`,key:`mv7ke4`}]]),Ee=P(`Download`,[[`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`,key:`ih7n3h`}],[`polyline`,{points:`7 10 12 15 17 10`,key:`2ggqvy`}],[`line`,{x1:`12`,x2:`12`,y1:`15`,y2:`3`,key:`1vk2je`}]]),De=P(`ExternalLink`,[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]),Oe=P(`Eye`,[[`path`,{d:`M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0`,key:`1nclc0`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),ke=P(`FileJson`,[[`path`,{d:`M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z`,key:`1rqfz7`}],[`path`,{d:`M14 2v4a2 2 0 0 0 2 2h4`,key:`tnqrlb`}],[`path`,{d:`M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1`,key:`1oajmo`}],[`path`,{d:`M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1`,key:`mpwhp6`}]]),Ae=P(`Film`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M7 3v18`,key:`bbkbws`}],[`path`,{d:`M3 7.5h4`,key:`zfgn84`}],[`path`,{d:`M3 12h18`,key:`1i2n21`}],[`path`,{d:`M3 16.5h4`,key:`1230mu`}],[`path`,{d:`M17 3v18`,key:`in4fa5`}],[`path`,{d:`M17 7.5h4`,key:`myr1c1`}],[`path`,{d:`M17 16.5h4`,key:`go4c1d`}]]),je=P(`Flag`,[[`path`,{d:`M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z`,key:`i9b6wo`}],[`line`,{x1:`4`,x2:`4`,y1:`22`,y2:`15`,key:`1cm3nv`}]]),Me=P(`Flame`,[[`path`,{d:`M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z`,key:`96xj49`}]]),Ne=P(`Handshake`,[[`path`,{d:`m11 17 2 2a1 1 0 1 0 3-3`,key:`efffak`}],[`path`,{d:`m14 14 2.5 2.5a1 1 0 1 0 3-3l-3.88-3.88a3 3 0 0 0-4.24 0l-.88.88a1 1 0 1 1-3-3l2.81-2.81a5.79 5.79 0 0 1 7.06-.87l.47.28a2 2 0 0 0 1.42.25L21 4`,key:`9pr0kb`}],[`path`,{d:`m21 3 1 11h-2`,key:`1tisrp`}],[`path`,{d:`M3 3 2 14l6.5 6.5a1 1 0 1 0 3-3`,key:`1uvwmv`}],[`path`,{d:`M3 4h8`,key:`1ep09j`}]]),Pe=P(`HardDrive`,[[`line`,{x1:`22`,x2:`2`,y1:`12`,y2:`12`,key:`1y58io`}],[`path`,{d:`M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z`,key:`oot6mr`}],[`line`,{x1:`6`,x2:`6.01`,y1:`16`,y2:`16`,key:`sgf278`}],[`line`,{x1:`10`,x2:`10.01`,y1:`16`,y2:`16`,key:`1l4acy`}]]),Fe=P(`History`,[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`1357e3`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}],[`path`,{d:`M12 7v5l4 2`,key:`1fdv2h`}]]),Ie=P(`ImageOff`,[[`line`,{x1:`2`,x2:`22`,y1:`2`,y2:`22`,key:`a6p6uj`}],[`path`,{d:`M10.41 10.41a2 2 0 1 1-2.83-2.83`,key:`1bzlo9`}],[`line`,{x1:`13.5`,x2:`6`,y1:`13.5`,y2:`21`,key:`1q0aeu`}],[`line`,{x1:`18`,x2:`21`,y1:`12`,y2:`15`,key:`5mozeu`}],[`path`,{d:`M3.59 3.59A1.99 1.99 0 0 0 3 5v14a2 2 0 0 0 2 2h14c.55 0 1.052-.22 1.41-.59`,key:`mmje98`}],[`path`,{d:`M21 15V5a2 2 0 0 0-2-2H9`,key:`43el77`}]]),Le=P(`ImagePlus`,[[`path`,{d:`M16 5h6`,key:`1vod17`}],[`path`,{d:`M19 2v6`,key:`4bpg5p`}],[`path`,{d:`M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5`,key:`1ue2ih`}],[`path`,{d:`m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21`,key:`1xmnt7`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}]]),Re=P(`LayoutDashboard`,[[`rect`,{width:`7`,height:`9`,x:`3`,y:`3`,rx:`1`,key:`10lvy0`}],[`rect`,{width:`7`,height:`5`,x:`14`,y:`3`,rx:`1`,key:`16une8`}],[`rect`,{width:`7`,height:`9`,x:`14`,y:`12`,rx:`1`,key:`1hutg5`}],[`rect`,{width:`7`,height:`5`,x:`3`,y:`16`,rx:`1`,key:`ldoo1y`}]]),ze=P(`LifeBuoy`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m4.93 4.93 4.24 4.24`,key:`1ymg45`}],[`path`,{d:`m14.83 9.17 4.24-4.24`,key:`1cb5xl`}],[`path`,{d:`m14.83 14.83 4.24 4.24`,key:`q42g0n`}],[`path`,{d:`m9.17 14.83-4.24 4.24`,key:`bqpfvv`}],[`circle`,{cx:`12`,cy:`12`,r:`4`,key:`4exip2`}]]),Be=P(`LogOut`,[[`path`,{d:`M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4`,key:`1uf3rs`}],[`polyline`,{points:`16 17 21 12 16 7`,key:`1gabdz`}],[`line`,{x1:`21`,x2:`9`,y1:`12`,y2:`12`,key:`1uyos4`}]]),Ve=P(`Mail`,[[`rect`,{width:`20`,height:`16`,x:`2`,y:`4`,rx:`2`,key:`18n3k1`}],[`path`,{d:`m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7`,key:`1ocrg3`}]]),He=P(`Megaphone`,[[`path`,{d:`m3 11 18-5v12L3 14v-3z`,key:`n962bs`}],[`path`,{d:`M11.6 16.8a3 3 0 1 1-5.8-1.6`,key:`1yl0tm`}]]),Ue=P(`MemoryStick`,[[`path`,{d:`M6 19v-3`,key:`1nvgqn`}],[`path`,{d:`M10 19v-3`,key:`iu8nkm`}],[`path`,{d:`M14 19v-3`,key:`kcehxu`}],[`path`,{d:`M18 19v-3`,key:`1vh91z`}],[`path`,{d:`M8 11V9`,key:`63erz4`}],[`path`,{d:`M16 11V9`,key:`fru6f3`}],[`path`,{d:`M12 11V9`,key:`ha00sb`}],[`path`,{d:`M2 15h20`,key:`16ne18`}],[`path`,{d:`M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1.1a2 2 0 0 0 0 3.837V17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-5.1a2 2 0 0 0 0-3.837Z`,key:`lhddv3`}]]),We=P(`MessageSquareText`,[[`path`,{d:`M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z`,key:`1lielz`}],[`path`,{d:`M13 8H7`,key:`14i4kc`}],[`path`,{d:`M17 12H7`,key:`16if0g`}]]),Ge=P(`MonitorSmartphone`,[[`path`,{d:`M18 8V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h8`,key:`10dyio`}],[`path`,{d:`M10 19v-3.96 3.15`,key:`1irgej`}],[`path`,{d:`M7 19h5`,key:`qswx4l`}],[`rect`,{width:`6`,height:`10`,x:`16`,y:`12`,rx:`2`,key:`1egngj`}]]),z=P(`Moon`,[[`path`,{d:`M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z`,key:`a7tn18`}]]),Ke=P(`Palette`,[[`circle`,{cx:`13.5`,cy:`6.5`,r:`.5`,fill:`currentColor`,key:`1okk4w`}],[`circle`,{cx:`17.5`,cy:`10.5`,r:`.5`,fill:`currentColor`,key:`f64h9f`}],[`circle`,{cx:`8.5`,cy:`7.5`,r:`.5`,fill:`currentColor`,key:`fotxhn`}],[`circle`,{cx:`6.5`,cy:`12.5`,r:`.5`,fill:`currentColor`,key:`qy21gx`}],[`path`,{d:`M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z`,key:`12rzf8`}]]),qe=P(`Phone`,[[`path`,{d:`M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z`,key:`foiqr5`}]]),Je=P(`Play`,[[`polygon`,{points:`6 3 20 12 6 21 6 3`,key:`1oa8hb`}]]),Ye=P(`Plus`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]),Xe=P(`PowerOff`,[[`path`,{d:`M18.36 6.64A9 9 0 0 1 20.77 15`,key:`dxknvb`}],[`path`,{d:`M6.16 6.16a9 9 0 1 0 12.68 12.68`,key:`1x7qb5`}],[`path`,{d:`M12 2v4`,key:`3427ic`}],[`path`,{d:`m2 2 20 20`,key:`1ooewy`}]]),Ze=P(`Power`,[[`path`,{d:`M12 2v10`,key:`mnfbl`}],[`path`,{d:`M18.4 6.6a9 9 0 1 1-12.77.04`,key:`obofu9`}]]),Qe=P(`Radio`,[[`path`,{d:`M4.9 19.1C1 15.2 1 8.8 4.9 4.9`,key:`1vaf9d`}],[`path`,{d:`M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5`,key:`u1ii0m`}],[`circle`,{cx:`12`,cy:`12`,r:`2`,key:`1c9p78`}],[`path`,{d:`M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5`,key:`1j5fej`}],[`path`,{d:`M19.1 4.9C23 8.8 23 15.1 19.1 19`,key:`10b0cb`}]]),B=P(`RefreshCw`,[[`path`,{d:`M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8`,key:`v9h5vc`}],[`path`,{d:`M21 3v5h-5`,key:`1q7to0`}],[`path`,{d:`M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16`,key:`3uifl3`}],[`path`,{d:`M8 16H3v5`,key:`1cv678`}]]),$e=P(`ScrollText`,[[`path`,{d:`M15 12h-5`,key:`r7krc0`}],[`path`,{d:`M15 8h-5`,key:`1khuty`}],[`path`,{d:`M19 17V5a2 2 0 0 0-2-2H4`,key:`zz82l3`}],[`path`,{d:`M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3`,key:`1ph1d7`}]]),V=P(`Search`,[[`circle`,{cx:`11`,cy:`11`,r:`8`,key:`4ej97u`}],[`path`,{d:`m21 21-4.3-4.3`,key:`1qie3q`}]]),et=P(`Send`,[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]),tt=P(`Server`,[[`rect`,{width:`20`,height:`8`,x:`2`,y:`2`,rx:`2`,ry:`2`,key:`ngkwjq`}],[`rect`,{width:`20`,height:`8`,x:`2`,y:`14`,rx:`2`,ry:`2`,key:`iecqi9`}],[`line`,{x1:`6`,x2:`6.01`,y1:`6`,y2:`6`,key:`16zg32`}],[`line`,{x1:`6`,x2:`6.01`,y1:`18`,y2:`18`,key:`nzw8ys`}]]),nt=P(`Settings2`,[[`path`,{d:`M20 7h-9`,key:`3s1dr2`}],[`path`,{d:`M14 17H5`,key:`gfn3mx`}],[`circle`,{cx:`17`,cy:`17`,r:`3`,key:`18b49y`}],[`circle`,{cx:`7`,cy:`7`,r:`3`,key:`dfmy0x`}]]),rt=P(`Settings`,[[`path`,{d:`M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z`,key:`1qme2f`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),it=P(`Share2`,[[`circle`,{cx:`18`,cy:`5`,r:`3`,key:`gq8acd`}],[`circle`,{cx:`6`,cy:`12`,r:`3`,key:`w7nqdw`}],[`circle`,{cx:`18`,cy:`19`,r:`3`,key:`1xt0gg`}],[`line`,{x1:`8.59`,x2:`15.42`,y1:`13.51`,y2:`17.49`,key:`47mynk`}],[`line`,{x1:`15.41`,x2:`8.59`,y1:`6.51`,y2:`10.49`,key:`1n3mei`}]]),at=P(`ShieldAlert`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`M12 8v4`,key:`1got3b`}],[`path`,{d:`M12 16h.01`,key:`1drbdi`}]]),ot=P(`ShieldCheck`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`m9 12 2 2 4-4`,key:`dzmm74`}]]),st=P(`ShieldOff`,[[`path`,{d:`m2 2 20 20`,key:`1ooewy`}],[`path`,{d:`M5 5a1 1 0 0 0-1 1v7c0 5 3.5 7.5 7.67 8.94a1 1 0 0 0 .67.01c2.35-.82 4.48-1.97 5.9-3.71`,key:`1jlk70`}],[`path`,{d:`M9.309 3.652A12.252 12.252 0 0 0 11.24 2.28a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1v7a9.784 9.784 0 0 1-.08 1.264`,key:`18rp1v`}]]),ct=P(`Smartphone`,[[`rect`,{width:`14`,height:`20`,x:`5`,y:`2`,rx:`2`,ry:`2`,key:`1yt0o3`}],[`path`,{d:`M12 18h.01`,key:`mhygvu`}]]),lt=P(`Smile`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M8 14s1.5 2 4 2 4-2 4-2`,key:`1y1vjs`}],[`line`,{x1:`9`,x2:`9.01`,y1:`9`,y2:`9`,key:`yxxnd0`}],[`line`,{x1:`15`,x2:`15.01`,y1:`9`,y2:`9`,key:`1p4y9e`}]]),ut=P(`Stamp`,[[`path`,{d:`M5 22h14`,key:`ehvnwv`}],[`path`,{d:`M19.27 13.73A2.5 2.5 0 0 0 17.5 13h-11A2.5 2.5 0 0 0 4 15.5V17a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-1.5c0-.66-.26-1.3-.73-1.77Z`,key:`1sy9ra`}],[`path`,{d:`M14 13V8.5C14 7 15 7 15 5a3 3 0 0 0-3-3c-1.66 0-3 1-3 3s1 2 1 3.5V13`,key:`cnxgux`}]]),dt=P(`Sticker`,[[`path`,{d:`M15.5 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V8.5L15.5 3Z`,key:`1wis1t`}],[`path`,{d:`M14 3v4a2 2 0 0 0 2 2h4`,key:`36rjfy`}],[`path`,{d:`M8 13h.01`,key:`1sbv64`}],[`path`,{d:`M16 13h.01`,key:`wip0gl`}],[`path`,{d:`M10 16s.8 1 2 1c1.3 0 2-1 2-1`,key:`1vvgv3`}]]),ft=P(`Sun`,[[`circle`,{cx:`12`,cy:`12`,r:`4`,key:`4exip2`}],[`path`,{d:`M12 2v2`,key:`tus03m`}],[`path`,{d:`M12 20v2`,key:`1lh1kg`}],[`path`,{d:`m4.93 4.93 1.41 1.41`,key:`149t6j`}],[`path`,{d:`m17.66 17.66 1.41 1.41`,key:`ptbguv`}],[`path`,{d:`M2 12h2`,key:`1t8f8n`}],[`path`,{d:`M20 12h2`,key:`1q8mjw`}],[`path`,{d:`m6.34 17.66-1.41 1.41`,key:`1m8zz5`}],[`path`,{d:`m19.07 4.93-1.41 1.41`,key:`1shlcs`}]]),pt=P(`Trash2`,[[`path`,{d:`M3 6h18`,key:`d0wm0j`}],[`path`,{d:`M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6`,key:`4alrt4`}],[`path`,{d:`M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2`,key:`v07s0e`}],[`line`,{x1:`10`,x2:`10`,y1:`11`,y2:`17`,key:`1uufr5`}],[`line`,{x1:`14`,x2:`14`,y1:`11`,y2:`17`,key:`xtxkd`}]]),mt=P(`Undo2`,[[`path`,{d:`M9 14 4 9l5-5`,key:`102s5s`}],[`path`,{d:`M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11`,key:`f3b9sd`}]]),ht=P(`Upload`,[[`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`,key:`ih7n3h`}],[`polyline`,{points:`17 8 12 3 7 8`,key:`t8dd8p`}],[`line`,{x1:`12`,x2:`12`,y1:`3`,y2:`15`,key:`widbto`}]]),gt=P(`User`,[[`path`,{d:`M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2`,key:`975kel`}],[`circle`,{cx:`12`,cy:`7`,r:`4`,key:`17ys0d`}]]),_t=P(`Users`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}],[`path`,{d:`M22 21v-2a4 4 0 0 0-3-3.87`,key:`kshegd`}],[`path`,{d:`M16 3.13a4 4 0 0 1 0 7.75`,key:`1da9ce`}]]),vt=P(`Vault`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`circle`,{cx:`7.5`,cy:`7.5`,r:`.5`,fill:`currentColor`,key:`kqv944`}],[`path`,{d:`m7.9 7.9 2.7 2.7`,key:`hpeyl3`}],[`circle`,{cx:`16.5`,cy:`7.5`,r:`.5`,fill:`currentColor`,key:`w0ekpg`}],[`path`,{d:`m13.4 10.6 2.7-2.7`,key:`264c1n`}],[`circle`,{cx:`7.5`,cy:`16.5`,r:`.5`,fill:`currentColor`,key:`nkw3mc`}],[`path`,{d:`m7.9 16.1 2.7-2.7`,key:`p81g5e`}],[`circle`,{cx:`16.5`,cy:`16.5`,r:`.5`,fill:`currentColor`,key:`fubopw`}],[`path`,{d:`m13.4 13.4 2.7 2.7`,key:`abhel3`}],[`circle`,{cx:`12`,cy:`12`,r:`2`,key:`1c9p78`}]]),yt=P(`X`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),bt=P(`Zap`,[[`path`,{d:`M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z`,key:`1xq2db`}]]);function xt(e){let t=e.trim();return!t||t.startsWith(`+`)?t:/^\d+$/.test(t)?`+${t}`:t}function H(e){let t=e.trim();return t?t.startsWith(`@`)?t:`@${t}`:``}function St(e){return`${e.FirstName||``} ${e.LastName||``}`.trim()||`-`}function Ct(e){return e.Broadcast&&!e.Megagroup?`Channel`:e.Megagroup&&e.Forum?`Supergroup / Forum`:e.Megagroup?`Supergroup`:`Channel / Group`}function U(e){if(!e||e.startsWith(`0001-`))return``;let t=new Date(e);return Number.isNaN(t.getTime())?``:t.toLocaleString()}function wt(e){if(!e||e<=0)return``;let t=new Date(e*1e3);return Number.isNaN(t.getTime())?``:t.toLocaleString()}function Tt(e){let t=(e??``).trim();if(!/^https?:\/\//i.test(t))return``;try{let e=new URL(t);return e.protocol!==`http:`&&e.protocol!==`https:`?``:e.href}catch{return``}}function Et(e){if(!e.trim())return 0;let t=Number.parseInt(e,10);return Number.isFinite(t)?t:0}function Dt(e){let t=(e??``).trim();if(!t)return`0`;let n=Number(t);return Number.isFinite(n)?n.toLocaleString():t}var Ot={XTR:0,TON:9,USD:2,EUR:2,RUB:2};function kt(e){let t=(e??``).trim().toUpperCase();return t in Ot?Ot[t]:2}function At(e,t){let n=(e??``).trim();if(!n)return`0`;if(!/^-?\d+$/.test(n))return n;let r=kt(t),i=n.startsWith(`-`),a=(i?n.slice(1):n).replace(/^0+(?=\d)/,``).padStart(r+1,`0`),o=a.slice(0,a.length-r)||`0`,s=r>0?a.slice(a.length-r):``;r>2&&(s=s.replace(/0+$/,``));let c=i?`-`:``;return s?`${c}${jt(o)}.${s}`:`${c}${jt(o)}`}function jt(e){return e.replace(/\B(?=(\d{3})+(?!\d))/g,` `)}function Mt(e,t){let n=(t??``).trim().toUpperCase(),r=At(e,n);return n?`${r} ${n}`:r}function Nt(e,t){let n=(e??``).trim().replace(/\s+/g,``).replace(`,`,`.`);if(!n)return`0`;if(!/^\d*(\.\d*)?$/.test(n)||n===`.`)return null;let r=kt(t),[i,a=``]=n.split(`.`);if(a.length>r)return null;let o=`${i||`0`}${a.padEnd(r,`0`)}`.replace(/^0+(?=\d)/,``);return o===``?`0`:o}function Pt(e){let t=(e??``).trim();if(!t||!/^\d+$/.test(t))return`0 B`;let n=Number(t);if(!Number.isFinite(n))return`${t} B`;let r=[`B`,`KB`,`MB`,`GB`,`TB`,`PB`],i=n,a=0;for(;i>=1024&&ae.trim()).filter(Boolean).map(e=>Number.parseInt(e,10));if(n.length===0||n.some(e=>!Number.isFinite(e)||e<=0))throw Error(t);return n}var It=o((e=>{var t=u(),n=Symbol.for(`react.element`),r=Symbol.for(`react.fragment`),i=Object.prototype.hasOwnProperty,a=t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,o={key:!0,ref:!0,__self:!0,__source:!0};function s(e,t,r){var s,c={},l=null,u=null;for(s in r!==void 0&&(l=``+r),t.key!==void 0&&(l=``+t.key),t.ref!==void 0&&(u=t.ref),t)i.call(t,s)&&!o.hasOwnProperty(s)&&(c[s]=t[s]);if(e&&e.defaultProps)for(s in t=e.defaultProps,t)c[s]===void 0&&(c[s]=t[s]);return{$$typeof:n,type:e,key:l,ref:u,props:c,_owner:a.current}}e.Fragment=r,e.jsx=s,e.jsxs=s})),W=o(((e,t)=>{t.exports=It()}))();function G({title:e,eyebrow:t,children:n,actions:r}){return(0,W.jsxs)(`div`,{className:`page-frame`,children:[(0,W.jsxs)(`div`,{className:`page-title-row`,children:[(0,W.jsxs)(`div`,{children:[t&&(0,W.jsx)(`div`,{className:`eyebrow`,children:t}),(0,W.jsx)(`h2`,{children:e})]}),r&&(0,W.jsx)(`div`,{className:`page-actions`,children:r})]}),n]})}function Lt({children:e}){return(0,W.jsx)(`div`,{className:`query-panel`,children:e})}function Rt({main:e,side:t}){return(0,W.jsxs)(`div`,{className:`split-layout`,children:[(0,W.jsx)(`div`,{className:`split-main`,children:e}),(0,W.jsx)(`aside`,{className:`split-side`,children:t})]})}function K({title:e,text:t,action:n}){return(0,W.jsxs)(`div`,{className:`section-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`h2`,{children:e}),t&&(0,W.jsx)(`p`,{children:t})]}),n&&(0,W.jsx)(`div`,{className:`section-action`,children:n})]})}function q({children:e}){return(0,W.jsxs)(`div`,{className:`alert`,children:[(0,W.jsx)(te,{size:16}),` `,(0,W.jsx)(`span`,{children:e})]})}function J({children:e,tone:t=`neutral`}){return(0,W.jsx)(`span`,{className:`badge ${t}`,children:e})}function Y({label:e,value:t,tone:n=`neutral`,mono:r=!1}){return(0,W.jsxs)(`div`,{className:`metric ${n}`,children:[(0,W.jsx)(`span`,{children:e}),(0,W.jsx)(`strong`,{className:r?`mono`:``,children:t})]})}function X({label:e,value:t,mono:n=!1}){return(0,W.jsxs)(`div`,{className:`summary-item`,children:[(0,W.jsx)(`span`,{children:e}),(0,W.jsx)(`strong`,{className:n?`mono`:``,children:t})]})}function zt({rows:e}){return(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Command ID`}),(0,W.jsx)(`th`,{children:`Action`}),(0,W.jsx)(`th`,{children:`Actor`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Dry-run`}),(0,W.jsx)(`th`,{children:`Reason`}),(0,W.jsx)(`th`,{children:`Time`})]})}),(0,W.jsxs)(`tbody`,{children:[e.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{children:e.ID}),(0,W.jsx)(`td`,{className:`mono`,children:e.CommandID}),(0,W.jsx)(`td`,{children:e.Action}),(0,W.jsx)(`td`,{children:e.Actor}),(0,W.jsx)(`td`,{children:e.Status}),(0,W.jsx)(`td`,{children:e.DryRun?`Yes`:`No`}),(0,W.jsx)(`td`,{className:`truncate`,children:e.Reason}),(0,W.jsx)(`td`,{children:U(e.CreatedAt)})]},e.ID)),e.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})})}function Bt({colSpan:e}){return(0,W.jsx)(`tr`,{children:(0,W.jsx)(`td`,{colSpan:e,className:`empty-cell`,children:`No results`})})}function Vt({label:e}){return(0,W.jsx)(`section`,{className:`surface`,children:(0,W.jsx)(`div`,{className:`loading-line`,children:e})})}function Ht({value:e}){return(0,W.jsx)(`pre`,{className:`json-block`,children:e||`{}`})}function Ut({username:e,collectibles:t}){let n=H(e??``),r=t??[];return r.length===0?(0,W.jsx)(W.Fragment,{children:n||`-`}):(0,W.jsxs)(W.Fragment,{children:[n,(0,W.jsx)(`ul`,{className:`username-branch`,children:r.map(e=>(0,W.jsxs)(`li`,{className:e.Active?``:`inactive`,children:[(0,W.jsx)(`span`,{children:H(e.Username)}),!e.Active&&(0,W.jsx)(`em`,{children:`inactive`})]},e.Username))})]})}var Wt=`verification.review`,Gt=`botverification.review`,Kt=`botverification.manage`,qt=`server.manage`,Jt=(0,g.createContext)({permissions:[],hideThirdPartyVerification:!0});function Yt({permissions:e,hideThirdPartyVerification:t=!0,children:n}){let r=(0,g.useMemo)(()=>({permissions:e,hideThirdPartyVerification:t}),[e,t]);return(0,W.jsx)(Jt.Provider,{value:r,children:n})}function Xt(){let{permissions:e}=(0,g.useContext)(Jt);return(0,g.useMemo)(()=>({permissions:e,can:t=>e.includes(`*`)||e.includes(t)}),[e])}function Zt(e){return Xt().can(e)}function Qt(){return(0,g.useContext)(Jt).hideThirdPartyVerification}function $t({permission:e,children:t}){let{can:n}=Xt();return n(e)?(0,W.jsx)(W.Fragment,{children:t}):(0,W.jsx)(en,{permission:e})}function en({permission:e}){return(0,W.jsxs)(G,{title:`Not enough rights`,eyebrow:`Console / Access`,children:[(0,W.jsx)(q,{children:`This session was not granted the ${e} permission, so the section stays closed.`}),(0,W.jsx)(`section`,{className:`section-block`,children:(0,W.jsx)(`div`,{className:`entity-head`,children:(0,W.jsxs)(`div`,{children:[(0,W.jsxs)(`div`,{className:`entity-title`,children:[(0,W.jsx)(st,{size:16}),` `,`Section unavailable`]}),(0,W.jsx)(`div`,{className:`entity-subtitle`,children:`Ask an operator to add the permission to TELESRV_ADMIN_UI_PERMISSIONS and sign in again.`})]})})})]})}function tn({children:e}){return Qt()?(0,W.jsxs)(G,{title:`Feature hidden`,eyebrow:`Console / Third-party marks`,children:[(0,W.jsx)(q,{children:`Third-party bot verification is hidden on this server (TELESRV_HIDE_THIRD_PARTY_VERIFICATION=true).`}),(0,W.jsx)(`section`,{className:`section-block`,children:(0,W.jsx)(`div`,{className:`entity-head`,children:(0,W.jsxs)(`div`,{children:[(0,W.jsxs)(`div`,{className:`entity-title`,children:[(0,W.jsx)(st,{size:16}),` `,`Not fully finished`]}),(0,W.jsx)(`div`,{className:`entity-subtitle`,children:`This feature may cause unstable server behavior and is hidden by default. Set TELESRV_HIDE_THIRD_PARTY_VERIFICATION=false to re-enable it.`})]})})})]}):(0,W.jsx)(W.Fragment,{children:e})}function nn(){return{href:`${window.location.pathname}${window.location.search}`,path:window.location.pathname,search:new URLSearchParams(window.location.search)}}function rn(e){return e.startsWith(`/bot-verification`)?`Third-party verification`:e.startsWith(`/verification`)?`Official Verification`:e.startsWith(`/collectible-usernames`)?`Collectible Usernames`:e.startsWith(`/storage`)?`Storage`:e.startsWith(`/accounts/shared-devices`)?`Shared Devices`:e.startsWith(`/accounts`)?`Accounts`:e.startsWith(`/channels`)?`Supergroups and Channels`:e.startsWith(`/bots`)?`Bots`:e.startsWith(`/moderation`)?`Reports and Moderation`:e.startsWith(`/broadcasts`)?`Broadcasts`:e.startsWith(`/emoji`)?`Emoji`:e.startsWith(`/messages`)?`Message Audit`:e.startsWith(`/stickers`)?`Stickers`:e.startsWith(`/gif-catalog`)?`GIFs`:e.startsWith(`/server-settings`)?`Server Settings`:`Operations Console`}var an=`telesrv.admin.theme`,on=(0,g.createContext)(null);function sn(e){document.documentElement.setAttribute(`data-theme`,e),document.documentElement.style.colorScheme=e}function cn({children:e}){let[t,n]=(0,g.useState)(()=>dn());(0,g.useEffect)(()=>{sn(t);try{localStorage.setItem(an,t)}catch{}},[t]),(0,g.useEffect)(()=>{if(!window.matchMedia)return;let e=window.matchMedia(`(prefers-color-scheme: dark)`),t=e=>{let t=null;try{t=localStorage.getItem(an)}catch{t=null}t!==`light`&&t!==`dark`&&n(e.matches?`dark`:`light`)};return e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[]);let r=(0,g.useCallback)(e=>n(e),[]),i=(0,g.useCallback)(()=>n(e=>e===`dark`?`light`:`dark`),[]),a=(0,g.useMemo)(()=>({theme:t,setTheme:r,toggleTheme:i}),[t,r,i]);return(0,W.jsx)(on.Provider,{value:a,children:e})}function ln(){let e=(0,g.useContext)(on);if(!e)throw Error(`useTheme must be used inside ThemeProvider`);return e}function un(){let{theme:e,toggleTheme:t}=ln(),n=e===`light`?`Switch to dark theme`:`Switch to light theme`;return(0,W.jsx)(`button`,{className:`theme-toggle`,type:`button`,onClick:t,"aria-label":n,title:n,children:e===`dark`?(0,W.jsx)(ft,{size:16}):(0,W.jsx)(z,{size:16})})}function dn(){try{let e=localStorage.getItem(an);if(e===`light`||e===`dark`)return e}catch{}try{if(window.matchMedia&&window.matchMedia(`(prefers-color-scheme: dark)`).matches)return`dark`}catch{}return`light`}var fn=m();async function pn(e){if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(e);return}let t=document.createElement(`textarea`);t.value=e,t.style.position=`fixed`,t.style.top=`-1000px`,t.style.left=`-1000px`,document.body.appendChild(t),t.focus(),t.select();try{if(!document.execCommand(`copy`))throw Error(`Copy command was not successful`)}finally{document.body.removeChild(t)}}function mn({onClose:e}){let[t,n]=(0,g.useState)(null),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(!1);(0,g.useEffect)(()=>{let e=!1;return k.addServerLink().then(t=>{e||n(t.link)}).catch(t=>{e||i(O(t))}),()=>{e=!0}},[]);async function s(){if(t)try{await pn(t),o(!0)}catch(e){i(O(e))}}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal add-server-link-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Share server`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Server`}),(0,W.jsx)(`h2`,{children:`Share server`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:e,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsx)(`p`,{children:`Share this link (a button, a QR code, a message) so anyone with the OwpenGram client can add this server in one tap. It only carries the address and port -- the client fetches the name, description, and key directly from the server itself, so the link can never be tampered with to point someone at a fake identity for this address.`}),r&&(0,W.jsx)(q,{children:r}),!t&&!r&&(0,W.jsx)(Vt,{label:`Building link...`}),t&&(0,W.jsxs)(`div`,{className:`add-server-link-field`,children:[(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsx)(`span`,{children:`owpg:// link`}),(0,W.jsx)(`textarea`,{value:t,readOnly:!0,rows:2,onFocus:e=>e.currentTarget.select()})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`button`,onClick:()=>void s(),children:[(0,W.jsx)(R,{size:15}),` `,a?`Copy again`:`Copy link`]}),a&&(0,W.jsx)(`div`,{className:`secret-reveal`,children:(0,W.jsxs)(`div`,{className:`secret-reveal-label`,children:[(0,W.jsx)(ye,{size:14}),` `,`Copied to clipboard.`]})})]})]}),(0,W.jsx)(`div`,{className:`modal-actions`,children:(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:e,children:`Close`})})]})}),document.body)}function hn({href:e,navigate:t,className:n,children:r}){return(0,W.jsx)(`a`,{className:n,href:e,onClick:n=>{n.preventDefault(),t(e)},children:r})}function gn(e){let t=[...e].sort((e,t)=>e-t),n=[],r=t[0],i=t[0];for(let e=1;e<=t.length;e++){let a=t[e];if(a===i+1){i=a;continue}n.push(r===i?`${r}`:`${r}-${i}`),r=a,i=a}return n.join(`, `)}function _n(){return(0,W.jsxs)(`div`,{className:`boot-screen`,children:[(0,W.jsxs)(`div`,{className:`brand compact brand-elevated`,children:[(0,W.jsx)(`span`,{className:`brand-mark`,children:(0,W.jsx)(`img`,{src:`/logo.png`,alt:`OwpenGram`})}),(0,W.jsxs)(`span`,{children:[(0,W.jsx)(`strong`,{children:`OwpenGram`}),(0,W.jsx)(`small`,{children:`Admin Console`})]})]}),(0,W.jsx)(`div`,{className:`loader-bar`})]})}function vn({actor:e,apiLayers:t,build:n,route:r,navigate:i,onLogout:a,children:o}){let s=Zt(Wt),c=Zt(Gt),l=Zt(qt),[u,d]=(0,g.useState)(!1),[f,p]=(0,g.useState)(!1),[m,h]=(0,g.useState)(``);async function _(){p(!0),h(``);try{let e=await k.addServerLink();window.location.href=e.link}catch(e){h(O(e))}finally{p(!1)}}let[v,y]=(0,g.useState)(null);(0,g.useEffect)(()=>{l&&k.serverIdentity().then(e=>y({name:e.name,iconExt:e.icon_ext})).catch(()=>void 0)},[l]);let[b,x]=(0,g.useState)(!1),S=v?.name?.trim()||`OwpenGram`,C=v?.iconExt&&!b?k.serverIconURL():`/logo.png`;(0,g.useEffect)(()=>{document.title=`${S} Admin`},[S]),(0,g.useEffect)(()=>{let e=document.querySelector(`link[rel='icon']`);e||(e=document.createElement(`link`),e.rel=`icon`,document.head.appendChild(e));let t=e,n=v?.iconExt&&!b?k.serverIconURL():`/logo.png`,r=!1,i=new Image;return i.crossOrigin=`anonymous`,i.onload=()=>{if(r)return;let e=document.createElement(`canvas`);e.width=64,e.height=64;let a=e.getContext(`2d`);if(!a){t.href=n;return}a.save(),a.beginPath(),a.arc(64/2,64/2,64/2,0,Math.PI*2),a.closePath(),a.clip(),a.drawImage(i,0,0,64,64),a.restore(),t.href=e.toDataURL(`image/png`)},i.onerror=()=>{r||(t.href=n)},i.src=n,()=>{r=!0}},[v?.iconExt,b]);let w=Qt(),T=r.path.startsWith(`/messages`),[E,D]=(0,g.useState)(T);(0,g.useEffect)(()=>{T&&D(!0)},[T]);async function A(){await k.logout().catch(()=>void 0),a()}return(0,W.jsxs)(`div`,{className:`shell`,children:[(0,W.jsxs)(`aside`,{className:`sidebar`,children:[(0,W.jsxs)(hn,{className:`brand`,href:`/`,navigate:i,children:[(0,W.jsx)(`span`,{className:`brand-mark`,children:(0,W.jsx)(`img`,{src:C,alt:S,onError:()=>x(!0)})}),(0,W.jsxs)(`span`,{children:[(0,W.jsx)(`strong`,{children:S}),(0,W.jsx)(`small`,{children:`Admin Console`})]})]}),(0,W.jsx)(`div`,{className:`sidebar-label`,children:`Navigation`}),(0,W.jsxs)(`nav`,{className:`nav-list`,"aria-label":`Primary navigation`,children:[(0,W.jsx)(yn,{icon:(0,W.jsx)(Re,{size:16}),href:`/`,route:r,navigate:i,children:`Overview`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(_t,{size:16}),href:`/accounts`,route:r,navigate:i,children:`Accounts`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(ot,{size:16}),href:`/channels`,route:r,navigate:i,children:`Supergroups / Channels`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(ge,{size:16}),href:`/bots`,route:r,navigate:i,children:`Bots`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(at,{size:16}),href:`/moderation`,route:r,navigate:i,children:`Reports / Moderation`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(He,{size:16}),href:`/broadcasts`,route:r,navigate:i,children:`Broadcasts`}),s&&(0,W.jsx)(yn,{icon:(0,W.jsx)(ee,{size:16}),href:`/verification`,route:r,navigate:i,children:`Verification`}),c&&!w&&(0,W.jsx)(yn,{icon:(0,W.jsx)(ut,{size:16}),href:`/bot-verification`,route:r,navigate:i,children:`Third-party marks`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(me,{size:16}),href:`/collectible-usernames`,route:r,navigate:i,children:`NFT Usernames`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(Te,{size:16}),href:`/storage`,route:r,navigate:i,children:`Storage`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(dt,{size:16}),href:`/stickers`,route:r,navigate:i,children:`Stickers`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(lt,{size:16}),href:`/emoji`,route:r,navigate:i,children:`Emoji`}),(0,W.jsx)(yn,{icon:(0,W.jsx)(Ae,{size:16}),href:`/gif-catalog`,route:r,navigate:i,children:`GIFs`}),(0,W.jsxs)(`div`,{className:`nav-section ${T?`active`:``} ${E?`open`:``}`,children:[(0,W.jsxs)(`button`,{className:`nav-section-toggle`,type:`button`,"aria-expanded":E,onClick:()=>D(e=>!e),children:[(0,W.jsx)(We,{size:16}),(0,W.jsx)(`span`,{children:`Messages`}),(0,W.jsx)(be,{className:`nav-section-chevron`,size:15})]}),E&&(0,W.jsxs)(`div`,{className:`nav-children`,children:[(0,W.jsx)(yn,{href:`/messages/private`,route:r,navigate:i,activeWhen:e=>e===`/messages`||e===`/messages/detail`||e.startsWith(`/messages/private`),children:`Private`}),(0,W.jsx)(yn,{href:`/messages/groups`,route:r,navigate:i,activeWhen:e=>e.startsWith(`/messages/groups`),children:`Groups`})]})]}),l&&(0,W.jsx)(yn,{icon:(0,W.jsx)(rt,{size:16}),href:`/server-settings`,route:r,navigate:i,children:`Server Settings`})]}),(0,W.jsxs)(`div`,{className:`sidebar-status`,children:[(0,W.jsx)(`span`,{className:`sidebar-label`,children:`Version: O7`}),t&&t.length>0&&(0,W.jsx)(`span`,{className:`sidebar-label sidebar-api-layer`,title:`Layers: ${t.join(`, `)}`,children:`API layers: ${gn(t)}`}),n?.short_commit&&(0,W.jsx)(`span`,{className:`sidebar-label sidebar-build`,title:n.commit+(n.dirty?` (uncommitted changes)`:``),children:`Build: ${n.short_commit}${n.dirty?`+`:``}`})]}),l&&(0,W.jsxs)(`div`,{className:`sidebar-server-actions`,children:[(0,W.jsxs)(`button`,{className:`btn ghost sidebar-server-action`,type:`button`,title:`Connect this browser's client to this server`,disabled:f,onClick:()=>void _(),children:[(0,W.jsx)(bt,{size:15}),` `,`Connect`]}),(0,W.jsxs)(`button`,{className:`btn ghost sidebar-server-action`,type:`button`,title:`Share server (get an add-server link)`,onClick:()=>d(!0),children:[(0,W.jsx)(it,{size:15}),` `,`Share`]})]}),m&&(0,W.jsx)(`div`,{className:`sidebar-server-action-error`,children:m}),u&&(0,W.jsx)(mn,{onClose:()=>d(!1)})]}),(0,W.jsxs)(`div`,{className:`workspace`,children:[(0,W.jsxs)(`header`,{className:`topbar`,children:[(0,W.jsx)(`div`,{children:(0,W.jsx)(`h1`,{children:rn(r.path)})}),(0,W.jsxs)(`div`,{className:`topbar-actions`,children:[(0,W.jsx)(un,{}),(0,W.jsx)(`span`,{className:`actor-pill`,children:`Actor: ${e}`}),(0,W.jsxs)(`button`,{className:`btn ghost icon-text`,type:`button`,onClick:A,title:`Log out`,children:[(0,W.jsx)(Be,{size:16}),` `,`Log out`]})]})]}),(0,W.jsx)(`main`,{className:`content`,children:o})]})]})}function yn({href:e,route:t,navigate:n,icon:r,children:i,activeWhen:a}){return(0,W.jsxs)(hn,{className:`nav-item ${(a?a(t.path):e===`/`?t.path===`/`:t.path.startsWith(e))?`active`:``}`,href:e,navigate:n,children:[r??(0,W.jsx)(`span`,{"aria-hidden":`true`,className:`nav-dot`}),(0,W.jsx)(`span`,{children:i})]})}function bn({onLogin:e}){let[t,n]=(0,g.useState)(``),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(!1);async function s(n){n.preventDefault(),o(!0),i(``);try{let n=await k.login(t);e({actor:n.actor,permissions:n.permissions??[]})}catch(e){i(O(e))}finally{o(!1)}}return(0,W.jsxs)(`main`,{className:`login-page`,children:[(0,W.jsxs)(`div`,{className:`bg-orbs`,"aria-hidden":`true`,children:[(0,W.jsx)(`div`,{className:`bg-orb bg-orb--1`}),(0,W.jsx)(`div`,{className:`bg-orb bg-orb--2`}),(0,W.jsx)(`div`,{className:`bg-orb bg-orb--3`})]}),(0,W.jsxs)(`section`,{className:`login-panel`,children:[(0,W.jsxs)(`div`,{className:`login-head`,children:[(0,W.jsxs)(`div`,{className:`brand brand-elevated`,children:[(0,W.jsx)(`span`,{className:`brand-mark`,children:(0,W.jsx)(`img`,{src:`/logo.png`,alt:`OwpenGram`})}),(0,W.jsxs)(`span`,{children:[(0,W.jsx)(`strong`,{children:`OwpenGram`}),(0,W.jsx)(`small`,{children:`Admin Console`})]})]}),(0,W.jsxs)(`div`,{className:`login-head-actions`,children:[(0,W.jsx)(un,{}),(0,W.jsx)(`span`,{className:`login-chip`,children:`Local access`})]})]}),(0,W.jsxs)(`div`,{className:`login-copy`,children:[(0,W.jsx)(`h1`,{children:`Operations Admin`}),(0,W.jsx)(`p`,{children:`Enter credentials to open the console.`})]}),r&&(0,W.jsx)(q,{children:r}),(0,W.jsxs)(`form`,{className:`form-stack`,onSubmit:s,children:[(0,W.jsxs)(`label`,{children:[(0,W.jsx)(`span`,{children:`Admin password or token`}),(0,W.jsx)(`input`,{autoFocus:!0,type:`password`,value:t,autoComplete:`current-password`,onChange:e=>n(e.target.value)})]}),(0,W.jsx)(`button`,{className:`btn primary full`,type:`submit`,disabled:a,children:a?`Logging in`:`Log in`})]})]})]})}function xn({kind:e,id:t,onClose:n,onDone:r}){let[i,a]=(0,g.useState)(null),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(``),[u,d]=(0,g.useState)(!1),[f,p]=(0,g.useState)(``);(0,g.useEffect)(()=>{if(!i){s(``);return}let e=URL.createObjectURL(i);return s(e),()=>URL.revokeObjectURL(e)},[i]);async function m(){if(!i){p(`Choose an image file first.`);return}if(!c.trim()){p(`Please enter an operation reason`);return}d(!0),p(``);try{let a=e===`channel`?`channel_id`:`user_id`,o=new FormData;o.set(`metadata`,JSON.stringify({command_id:``,reason:c.trim(),confirm:!0,[a]:t})),o.set(`file`,i,i.name);let s=e===`channel`?await k.setChannelAvatar(o):await k.setAccountAvatar(o);if(s.error){p(s.error);return}r(),n()}catch(e){p(O(e))}finally{d(!1)}}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Change avatar`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:e===`channel`?`Channel`:`Account`}),(0,W.jsx)(`h2`,{children:`Change avatar`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:n,disabled:u,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsxs)(`label`,{className:`gift-file-picker ${i?`has-file`:``}`,children:[(0,W.jsx)(`input`,{type:`file`,accept:`image/png,image/jpeg,image/webp`,onChange:e=>a(e.target.files?.[0]??null)}),o?(0,W.jsx)(`img`,{className:`gift-file-icon`,src:o,alt:``,style:{objectFit:`cover`}}):(0,W.jsx)(Le,{size:22}),(0,W.jsxs)(`span`,{className:`gift-file-copy`,children:[(0,W.jsx)(`span`,{className:`gift-field-label`,children:`New avatar`}),(0,W.jsx)(`strong`,{children:i?i.name:`Choose a JPEG, PNG, or WebP image`})]}),(0,W.jsx)(`span`,{className:`gift-file-action`,children:i?`Change file`:`Choose file`})]}),(0,W.jsxs)(`label`,{className:`gift-reason-field`,children:[(0,W.jsx)(`span`,{children:`Audit reason`}),(0,W.jsx)(`input`,{value:c,placeholder:`Briefly describe why this avatar is being changed`,onChange:e=>l(e.target.value)})]}),f&&(0,W.jsx)(q,{children:f})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:n,disabled:u,children:`Close`}),(0,W.jsxs)(`button`,{className:`btn primary`,type:`button`,onClick:m,disabled:u,children:[u?(0,W.jsx)(I,{className:`spin`,size:15}):(0,W.jsx)(ht,{size:15}),`Upload avatar`]})]})]})}),document.body)}function Z({label:e,path:t,payload:n,icon:r,compact:i=!1,tone:a=`danger`,disabled:o=!1,onDone:s,onError:c,secretField:l}){let[u,d]=(0,g.useState)(!1),[f,p]=(0,g.useState)(``),[m,h]=(0,g.useState)(null),[_,v]=(0,g.useState)(``),[y,b]=(0,g.useState)(!1),[x,S]=(0,g.useState)(!1);function C(){p(``),h(null),v(``),S(!1)}async function w(e){if(!f.trim()){v(`Please enter an operation reason`);return}b(!0),v(``);try{let r={...n(),reason:f,confirm:e};h(await k.action(t,r)),e&&s?.()}catch(e){v(c?.(e)||O(e))}finally{b(!1)}}let T=m?.dry_run&&!m.error,E=`btn ${a===`danger`?`danger`:a===`warn`?`warn`:a===`primary`?`primary`:``} ${i?`compact-btn`:``}`,D=(0,g.useMemo)(()=>{try{return n()}catch(e){return{payload_error:O(e)}}},[u,n]),A=l&&m?.details&&typeof m.details[l]==`string`?m.details[l]:``,j=A&&m?.details?Object.fromEntries(Object.entries(m.details).filter(([e])=>e!==l)):m?.details;async function M(){await pn(A),S(!0)}return(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:E,type:`button`,disabled:o,onClick:()=>{C(),d(!0)},children:[r,e]}),u&&(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":e,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Action Flow`}),(0,W.jsx)(`h2`,{children:e})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:()=>d(!1),"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsxs)(`div`,{className:`command-steps`,children:[(0,W.jsxs)(`div`,{className:`command-step ${f.trim()?`done`:`active`}`,children:[(0,W.jsx)(`span`,{children:`1`}),(0,W.jsx)(`strong`,{children:`Enter reason`})]}),(0,W.jsxs)(`div`,{className:`command-step ${m?.dry_run?`done`:f.trim()?`active`:``}`,children:[(0,W.jsx)(`span`,{children:`2`}),(0,W.jsx)(`strong`,{children:`Dry-run check`})]}),(0,W.jsxs)(`div`,{className:`command-step ${m&&!m.dry_run&&!m.error?`done`:T?`active`:``}`,children:[(0,W.jsx)(`span`,{children:`3`}),(0,W.jsx)(`strong`,{children:`Confirm execution`})]})]}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsx)(`span`,{children:`Operation reason`}),(0,W.jsx)(`textarea`,{value:f,onChange:e=>p(e.target.value),rows:3,placeholder:`Describe why this operation is being performed`})]}),(0,W.jsxs)(`div`,{className:`command-preview`,children:[(0,W.jsxs)(`div`,{className:`preview-head`,children:[(0,W.jsx)(ke,{size:14}),` `,`Request preview`]}),(0,W.jsx)(Ht,{value:JSON.stringify(D,null,2)})]}),_&&(0,W.jsx)(q,{children:_}),m&&(0,W.jsxs)(`div`,{className:`result-box`,children:[(0,W.jsxs)(`div`,{className:`result-title`,children:[m.error?(0,W.jsx)(te,{size:16}):(0,W.jsx)(F,{size:16}),(0,W.jsx)(`strong`,{children:m.message||m.error||`Action result`})]}),(0,W.jsxs)(`div`,{className:`result-line`,children:[(0,W.jsx)(`span`,{children:`Command ID`}),(0,W.jsx)(`strong`,{children:m.command_id})]}),(0,W.jsxs)(`div`,{className:`result-line`,children:[(0,W.jsx)(`span`,{children:`Status`}),(0,W.jsx)(`strong`,{children:m.status})]}),(0,W.jsxs)(`div`,{className:`result-line`,children:[(0,W.jsx)(`span`,{children:`Dry-run`}),(0,W.jsx)(`strong`,{children:m.dry_run?`Yes`:`No`})]}),(0,W.jsx)(`div`,{className:`result-message`,children:m.message||m.error}),A&&(0,W.jsxs)(`div`,{className:`secret-reveal`,children:[(0,W.jsx)(`div`,{className:`secret-reveal-label`,children:`One-time secret — copy it now, it won't be shown again`}),(0,W.jsxs)(`div`,{className:`secret-reveal-row`,children:[(0,W.jsx)(`code`,{className:`secret-reveal-value`,children:`•`.repeat(Math.min(A.length,40))}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>void M(),children:[x?(0,W.jsx)(ye,{size:15}):(0,W.jsx)(R,{size:15}),x?`Copied`:`Copy`]})]})]}),j&&Object.keys(j).length>0&&(0,W.jsx)(Ht,{value:JSON.stringify(j,null,2)})]})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:()=>d(!1),children:`Close`}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>w(!1),disabled:y,children:[y?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(Je,{size:15}),m?`Run dry-run again`:`Run dry-run first`]}),(0,W.jsxs)(`button`,{className:`btn danger icon-text`,type:`button`,onClick:()=>w(!0),disabled:y||!T,children:[(0,W.jsx)(F,{size:15}),`Confirm execution`]})]})]})}),document.body)]})}var Sn=[[`#FF885E`,`#FF516A`],[`#FFCD6A`,`#FFA85C`],[`#82B1FF`,`#665FFF`],[`#A0DE7E`,`#54CB68`],[`#53EDD6`,`#28C9B7`],[`#72D5FD`,`#2A9EF1`],[`#E0A2F3`,`#D669ED`]];function Cn(e){return Sn[Math.abs(e)%Sn.length]}function wn(e){let t=Array.from(e);return t.length>0?t[0]:``}function Tn(e,t,n){let r=`${e} ${t}`.trim().split(/\s+/).filter(Boolean),i=r.length>0?r:n?[n]:[];if(i.length===0)return`T`;let a=wn(i[0]);return i.length>1&&(a+=wn(i[i.length-1])),a.toUpperCase()}function En({id:e,kind:t=`user`,firstName:n=``,lastName:r=``,username:i=``,title:a=``,size:o=34,refreshKey:s}){let[c,l]=(0,g.useState)(!1);if((0,g.useEffect)(()=>{l(!1)},[e,t,s]),c){let[s,c]=Cn(e);return(0,W.jsx)(`div`,{className:`avatar-fallback`,style:{width:o,height:o,background:`linear-gradient(135deg, ${s}, ${c})`,fontSize:Math.round(o*.42)},children:t===`channel`?Tn(a,``,i):Tn(n,r,i)})}return(0,W.jsx)(`img`,{className:`avatar-photo-img`,src:`${t===`channel`?`/api/channels/${e}/avatar`:`/api/accounts/${e}/avatar`}${s===void 0?``:`?v=${encodeURIComponent(String(s))}`}`,alt:``,loading:`lazy`,style:{width:o,height:o},onError:()=>l(!0)})}function Dn({rows:e,userID:t,onDone:n}){let[r,i]=(0,g.useState)(()=>new Set);(0,g.useEffect)(()=>{i(new Set)},[t]);let a=(0,g.useMemo)(()=>e.filter(e=>!r.has(e.Hash)),[e,r]);function o(e){i(t=>e(t)),n()}return(0,W.jsxs)(`div`,{className:`authorization-block`,children:[(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table authorization-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Device`}),(0,W.jsx)(`th`,{children:`Platform`}),(0,W.jsx)(`th`,{children:`IP`}),(0,W.jsx)(`th`,{children:`Last active`}),(0,W.jsx)(`th`,{className:`device-actions-head`,children:`Actions`})]})}),(0,W.jsxs)(`tbody`,{children:[a.map(n=>(0,W.jsxs)(`tr`,{children:[(0,W.jsxs)(`td`,{className:`device-text`,children:[n.DeviceModel,` `,n.SystemVersion]}),(0,W.jsxs)(`td`,{className:`device-text`,children:[n.Platform,` `,n.AppVersion]}),(0,W.jsx)(`td`,{children:n.IP}),(0,W.jsx)(`td`,{children:U(n.ActiveAt)}),(0,W.jsx)(`td`,{className:`device-actions-cell`,children:(0,W.jsxs)(`div`,{className:`device-actions`,children:[(0,W.jsx)(Z,{label:`Revoke current`,icon:(0,W.jsx)(Be,{size:13}),compact:!0,path:`/api/actions/revoke-sessions`,payload:()=>({user_id:t,hash:n.Hash}),onDone:()=>o(e=>new Set([...e,n.Hash]))}),(0,W.jsx)(Z,{label:`Keep current`,icon:(0,W.jsx)(ot,{size:13}),compact:!0,path:`/api/actions/revoke-sessions`,payload:()=>({user_id:t,keep_hash:n.Hash}),onDone:()=>o(()=>new Set(e.filter(e=>e.Hash!==n.Hash).map(e=>e.Hash)))})]})})]},n.Hash)),a.length===0&&(0,W.jsx)(Bt,{colSpan:5})]})]})}),(0,W.jsx)(`div`,{className:`danger-zone`,children:(0,W.jsx)(Z,{label:`Revoke all devices`,icon:(0,W.jsx)(ve,{size:15}),path:`/api/actions/revoke-sessions`,payload:()=>({user_id:t,revoke_all:!0}),onDone:()=>o(()=>new Set(e.map(e=>e.Hash)))})})]})}function On({scam:e,fake:t}){return!e&&!t?null:(0,W.jsxs)(W.Fragment,{children:[e&&(0,W.jsx)(J,{tone:`danger`,children:`SCAM`}),t&&(0,W.jsx)(J,{tone:`danger`,children:`FAKE`})]})}function kn({idKey:e,id:t,path:n,scam:r,fake:i,onDone:a}){return(0,W.jsxs)(`div`,{className:`action-stack`,children:[(0,W.jsx)(Z,{label:r?`Clear SCAM`:`Mark as SCAM`,icon:(0,W.jsx)(at,{size:15}),tone:`danger`,path:n,payload:()=>({[e]:t,scam:!r,fake:r?i:!1}),onDone:a}),(0,W.jsx)(Z,{label:i?`Clear FAKE`:`Mark as FAKE`,icon:(0,W.jsx)(ie,{size:15}),tone:`danger`,path:n,payload:()=>({[e]:t,fake:!i,scam:i?r:!1}),onDone:a})]})}function An({id:e,support:t,onDone:n}){return(0,W.jsx)(Z,{label:t?`Clear support`:`Mark as support`,icon:(0,W.jsx)(ze,{size:15}),tone:`neutral`,path:`/api/actions/set-support`,payload:()=>({user_id:e,support:!t}),onDone:n})}function jn({idKey:e,id:t,path:n,current:r,onDone:i}){let[a,o]=(0,g.useState)(r.replace(/^@/,``));return(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Username`}),(0,W.jsx)(`input`,{value:a,onChange:e=>o(e.target.value),placeholder:`username`})]}),(0,W.jsx)(Z,{label:`Set username`,icon:(0,W.jsx)(me,{size:15}),tone:`neutral`,path:n,payload:()=>({[e]:t,username:a.trim().replace(/^@/,``)}),onDone:i})]})}function Mn({id:e,path:t,currentFirstName:n,currentLastName:r,onDone:i}){let[a,o]=(0,g.useState)(n),[s,c]=(0,g.useState)(r);return(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`First name`}),(0,W.jsx)(`input`,{value:a,onChange:e=>o(e.target.value),placeholder:`First name`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Last name`}),(0,W.jsx)(`input`,{value:s,onChange:e=>c(e.target.value),placeholder:`Last name`})]}),(0,W.jsx)(Z,{label:`Set name`,icon:(0,W.jsx)(se,{size:15}),tone:`neutral`,path:t,payload:()=>({user_id:e,first_name:a.trim(),last_name:s.trim()}),onDone:i})]})}function Nn({id:e,path:t,current:n,onDone:r}){let[i,a]=(0,g.useState)(n);return(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Phone number`}),(0,W.jsx)(`input`,{value:i,onChange:e=>a(e.target.value),placeholder:`15551234567`})]}),(0,W.jsx)(Z,{label:`Set phone`,icon:(0,W.jsx)(qe,{size:15}),tone:`warn`,path:t,payload:()=>({user_id:e,phone:i.trim()}),onDone:r})]})}function Pn({id:e,path:t,current:n,onDone:r}){let[i,a]=(0,g.useState)(n);return(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Login email`}),(0,W.jsx)(`input`,{value:i,onChange:e=>a(e.target.value),placeholder:`name@example.com (empty clears it)`,type:`email`})]}),(0,W.jsx)(Z,{label:i.trim()?`Set login email`:`Clear login email`,icon:(0,W.jsx)(Ve,{size:15}),tone:`warn`,path:t,payload:()=>({user_id:e,email:i.trim()}),onDone:r})]})}function Fn({idKey:e,id:t,path:n,onDone:r}){let[i,a]=(0,g.useState)(!1),[o,s]=(0,g.useState)(!0),[c,l]=(0,g.useState)(`0`),[u,d]=(0,g.useState)(``);return(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:i,onChange:e=>a(e.target.checked)}),` `,`Profile color`]}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:o,onChange:e=>s(e.target.checked)}),` `,`Enable color`]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Color index`}),(0,W.jsx)(`input`,{type:`number`,min:`0`,max:`20`,value:c,onChange:e=>l(e.target.value)})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Background emoji ID`}),(0,W.jsx)(`input`,{value:u,onChange:e=>d(e.target.value),placeholder:`0`})]}),(0,W.jsx)(Z,{label:`Set color`,icon:(0,W.jsx)(Ke,{size:15}),tone:`neutral`,path:n,payload:()=>({[e]:t,for_profile:i,has_color:o,color:Et(c),background_emoji_id:u.trim()||`0`}),onDone:r})]})}function In({idKey:e,id:t,path:n,onDone:r}){let[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(`0`);return(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Emoji document ID`}),(0,W.jsx)(`input`,{value:i,onChange:e=>a(e.target.value),placeholder:`0 = clear`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Until (unix, 0 = permanent)`}),(0,W.jsx)(`input`,{type:`number`,min:`0`,value:o,onChange:e=>s(e.target.value)})]}),(0,W.jsx)(Z,{label:`Set emoji status`,icon:(0,W.jsx)(lt,{size:15}),tone:`neutral`,path:n,payload:()=>({[e]:t,document_id:i.trim()||`0`,until:Et(o)}),onDone:r})]})}function Ln({channel:e,onDone:t}){let[n,r]=(0,g.useState)(e.Gigagroup),[i,a]=(0,g.useState)(e.AntiSpam),[o,s]=(0,g.useState)(e.ParticipantsHidden),[c,l]=(0,g.useState)(e.NoForwards),[u,d]=(0,g.useState)(e.JoinToSend),[f,p]=(0,g.useState)(e.JoinRequest),[m,h]=(0,g.useState)(String(e.SlowmodeSeconds));(0,g.useEffect)(()=>{r(e.Gigagroup),a(e.AntiSpam),s(e.ParticipantsHidden),l(e.NoForwards),d(e.JoinToSend),p(e.JoinRequest),h(String(e.SlowmodeSeconds))},[e]);function _(){let t={channel_id:e.ID};return n!==e.Gigagroup&&(t.gigagroup=n),i!==e.AntiSpam&&(t.antispam=i),o!==e.ParticipantsHidden&&(t.participants_hidden=o),c!==e.NoForwards&&(t.noforwards=c),u!==e.JoinToSend&&(t.join_to_send=u),f!==e.JoinRequest&&(t.join_request=f),Et(m)!==e.SlowmodeSeconds&&(t.slowmode_seconds=Et(m)),t}return(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:n,onChange:e=>r(e.target.checked)}),` `,`Gigagroup`]}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:i,onChange:e=>a(e.target.checked)}),` `,`Aggressive anti-spam`]}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:o,onChange:e=>s(e.target.checked)}),` `,`Hide members`]}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:c,onChange:e=>l(e.target.checked)}),` `,`Restrict forwarding`]}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:u,onChange:e=>d(e.target.checked)}),` `,`Join to send messages`]}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:f,onChange:e=>p(e.target.checked)}),` `,`Join by request`]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Slowmode (seconds)`}),(0,W.jsx)(`input`,{type:`number`,min:`0`,max:`86400`,value:m,onChange:e=>h(e.target.value)})]}),(0,W.jsx)(Z,{label:`Apply settings`,icon:(0,W.jsx)(nt,{size:15}),tone:`warn`,path:`/api/actions/set-channel-settings`,payload:_,onDone:t})]})}function Rn({id:e,navigate:t}){let[n,r]=(0,g.useState)(null),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(!1),[c,l]=(0,g.useState)(`profile`),[u,d]=(0,g.useState)(`1`),[f,p]=(0,g.useState)(()=>zn(new Date(Date.now()+7*864e5))),[m,h]=(0,g.useState)(``),[_,v]=(0,g.useState)(!1),[y,b]=(0,g.useState)(0);async function x(){s(!0),a(``);try{let t=await k.account(e);r(t),t.Restriction.Frozen&&(t.Restriction.Until&&p(zn(new Date(t.Restriction.Until))),h(t.Restriction.AppealURL||``))}catch(e){a(O(e))}finally{s(!1)}}if((0,g.useEffect)(()=>{x(),l(`profile`)},[e]),i)return(0,W.jsx)(q,{children:i});if(!n)return(0,W.jsx)(Vt,{label:o?`Loading account detail`:`Waiting for data`});let S=n.Account,C=[{key:`profile`,label:`Profile & Status`,icon:(0,W.jsx)(se,{size:15})},{key:`devices`,label:`Authorized Devices`,icon:(0,W.jsx)(Ge,{size:15})},{key:`actions`,label:`Actions & Management`,icon:(0,W.jsx)(nt,{size:15})}];return(0,W.jsxs)(G,{title:`Account #${S.ID}`,eyebrow:`Account Profile`,actions:(0,W.jsxs)(`button`,{className:`btn icon-text`,onClick:()=>t(`/accounts`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to list`]}),children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{className:`entity-head-main`,children:[(0,W.jsxs)(`div`,{className:`avatar-edit-slot`,children:[(0,W.jsx)(En,{id:S.ID,firstName:S.FirstName,lastName:S.LastName,username:S.Username,size:64,refreshKey:y||void 0}),(0,W.jsx)(`button`,{className:`icon-btn avatar-edit-btn`,type:`button`,"aria-label":`Change avatar`,title:`Change avatar`,onClick:()=>v(!0),children:(0,W.jsx)(Le,{size:13})})]}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:St(S)}),(0,W.jsxs)(`div`,{className:`entity-subtitle`,children:[H(S.Username)||`No username`,` · `,xt(S.Phone)||`No phone`]}),S.Collectibles?.length>0&&(0,W.jsx)(`div`,{className:`entity-subtitle`,children:(0,W.jsx)(Ut,{username:``,collectibles:S.Collectibles})})]})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[S.PremiumUntil>0?(0,W.jsx)(J,{tone:`good`,children:`Premium`}):(0,W.jsx)(J,{children:`Not premium`}),n.Verified?(0,W.jsx)(J,{tone:`good`,children:`Verified`}):(0,W.jsx)(J,{children:`Not verified`}),(0,W.jsx)(On,{scam:n.Scam,fake:n.Fake}),S.Frozen?(0,W.jsx)(J,{tone:`danger`,children:`Account frozen`}):(0,W.jsx)(J,{children:`Account active`})]})]}),(0,W.jsx)(`div`,{className:`toolbar`,role:`group`,"aria-label":`Account sections`,children:C.map(e=>(0,W.jsxs)(`button`,{className:`btn icon-text ${c===e.key?`primary`:``}`,type:`button`,"aria-pressed":c===e.key,onClick:()=>l(e.key),children:[e.icon,` `,e.label]},e.key))}),c===`profile`&&(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`User ID`,value:String(S.ID),mono:!0}),(0,W.jsx)(X,{label:`Last active`,value:wt(n.LastSeenAt)||`-`}),(0,W.jsx)(X,{label:`Premium expires`,value:S.PremiumUntil>0?wt(S.PremiumUntil):`None`}),(0,W.jsx)(X,{label:`Updated`,value:U(S.UpdatedAt)||`-`}),(0,W.jsx)(X,{label:`Authorized devices`,value:String(n.Authorizations.length)}),(0,W.jsx)(X,{label:`Account flags`,value:`support=${n.Support} bot=${n.Bot}`}),(0,W.jsx)(X,{label:`Restriction`,value:n.HasRestriction?n.Restriction.Reason||`Restricted`:`None`}),(0,W.jsx)(X,{label:`Frozen since`,value:n.Restriction.Since?U(n.Restriction.Since):`None`}),(0,W.jsx)(X,{label:`Appeal deadline`,value:n.Restriction.Until?U(n.Restriction.Until):`None`}),(0,W.jsx)(X,{label:`Appeal URL`,value:n.Restriction.AppealURL||`None`}),(0,W.jsx)(X,{label:`Created`,value:U(S.CreatedAt)||`-`})]}),n.About&&(0,W.jsx)(`p`,{className:`about-text`,children:n.About})]}),c===`devices`&&(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Authorized Devices`,text:`${n.Authorizations.length} authorizations`}),(0,W.jsx)(Dn,{rows:n.Authorizations,userID:S.ID,onDone:x})]}),c===`actions`&&(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`action-groups`,children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Freeze & Restriction`,text:`Blocks sign-in and marks the account for appeal review.`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Appeal deadline`}),(0,W.jsx)(`input`,{"aria-label":`Freeze appeal deadline`,value:f,onChange:e=>p(e.target.value),type:`datetime-local`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Appeal URL`}),(0,W.jsx)(`input`,{"aria-label":`Freeze appeal URL`,value:m,onChange:e=>h(e.target.value),type:`url`,placeholder:`https://...`})]}),(0,W.jsx)(Z,{label:S.Frozen?`Update freeze`:`Freeze account`,icon:(0,W.jsx)(te,{size:15}),tone:`danger`,path:`/api/actions/set-frozen`,payload:()=>({user_id:S.ID,frozen:!0,freeze_until:new Date(f).toISOString(),freeze_appeal_url:m.trim()}),onDone:x}),S.Frozen&&(0,W.jsx)(Z,{label:`Unfreeze account`,icon:(0,W.jsx)(te,{size:15}),path:`/api/actions/set-frozen`,payload:()=>({user_id:S.ID,frozen:!1}),onDone:x})]})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Premium`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Premium duration (months)`}),(0,W.jsx)(`input`,{"aria-label":`Set premium duration in months`,value:u,onChange:e=>d(e.target.value),type:`number`,min:`1`,max:`120`})]}),(0,W.jsxs)(`div`,{className:`action-stack`,children:[(0,W.jsx)(Z,{label:`Set premium`,icon:(0,W.jsx)(ae,{size:15}),tone:`warn`,path:`/api/actions/grant-premium`,payload:()=>({user_id:S.ID,months:Et(u)}),onDone:x}),(0,W.jsx)(Z,{label:`Clear premium`,icon:(0,W.jsx)(ae,{size:15}),tone:`warn`,path:`/api/actions/grant-premium`,payload:()=>({user_id:S.ID,months:0}),onDone:x})]})]})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Verification & Moderation Flags`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsxs)(`div`,{className:`action-stack`,children:[(0,W.jsx)(Z,{label:n.Verified?`Clear verified`:`Set verified`,icon:(0,W.jsx)(ee,{size:15}),tone:`warn`,path:`/api/actions/set-verified`,payload:()=>({user_id:S.ID,verified:!n.Verified}),onDone:x}),(0,W.jsx)(kn,{idKey:`user_id`,id:S.ID,path:`/api/actions/set-account-flags`,scam:n.Scam,fake:n.Fake,onDone:x}),(0,W.jsx)(An,{id:S.ID,support:n.Support,onDone:x})]})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Username`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(jn,{idKey:`user_id`,id:S.ID,path:`/api/actions/set-account-username`,current:S.Username,onDone:x})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Name`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(Mn,{id:S.ID,path:`/api/actions/set-account-profile`,currentFirstName:S.FirstName,currentLastName:S.LastName,onDone:x})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Phone Number`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(Nn,{id:S.ID,path:`/api/actions/set-account-phone`,current:S.Phone,onDone:x})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Login Email`,text:`The email used for sign-in / password-recovery, not a contact address.`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(Pn,{id:S.ID,path:`/api/actions/set-account-login-email`,current:S.LoginEmail,onDone:x})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Profile Color`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(Fn,{idKey:`user_id`,id:S.ID,path:`/api/actions/set-account-color`,onDone:x})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Emoji Status`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(In,{idKey:`user_id`,id:S.ID,path:`/api/actions/set-account-emoji-status`,onDone:x})})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Recent Admin Actions`,text:`Last 30 audit rows`,action:(0,W.jsx)($e,{size:16})}),(0,W.jsx)(zt,{rows:n.AuditLogs})]})]}),_&&(0,W.jsx)(xn,{kind:`user`,id:S.ID,onClose:()=>v(!1),onDone:()=>{b(e=>e+1),x()}})]})}function zn(e){return new Date(e.getTime()-e.getTimezoneOffset()*6e4).toISOString().slice(0,16)}function Bn(e){return e.reduce((e,t)=>(e.devices+=t.DeviceCount,e),{devices:0})}function Vn(e){return e.reduce((e,t)=>(t.Megagroup&&(e.megagroups+=1),t.Broadcast&&(e.broadcasts+=1),t.Verified&&(e.verified+=1),e),{megagroups:0,broadcasts:0,verified:0})}var Hn={beforeID:0,beforeActiveUS:0};function Un({navigate:e}){let[t,n]=(0,g.useState)(``),[r,i]=(0,g.useState)(50),[a,o]=(0,g.useState)(null),[s,c]=(0,g.useState)(null),[l,u]=(0,g.useState)([]),[d,f]=(0,g.useState)(Hn),[p,m]=(0,g.useState)(!1),[h,_]=(0,g.useState)(``);async function v(e,t){m(!0),_(``);let n=new URLSearchParams({limit:String(r)});e.trim()&&n.set(`q`,e.trim()),(t.beforeID||t.beforeActiveUS)&&(n.set(`before_id`,String(t.beforeID)),n.set(`before_active_us`,String(t.beforeActiveUS)));try{let e=await k.accounts(n);return o(e),e}catch(e){return _(O(e)),null}finally{m(!1)}}async function y(){u([]),f(Hn),await v(t,Hn)}async function b(){if(!a?.has_more)return;let e={beforeID:a.next_before_id,beforeActiveUS:a.next_before_active_us};await v(t,e)&&(u(e=>[...e,d]),f(e))}async function x(){if(l.length===0)return;let e=l[l.length-1];await v(t,e)&&(u(e=>e.slice(0,-1)),f(e))}async function S(){try{c(await k.accountStats())}catch{}}(0,g.useEffect)(()=>{y(),S()},[]);let C=Bn(a?.rows??[]),w=l.length>0&&!p,T=!!a?.has_more&&!p;return(0,W.jsxs)(G,{title:`Accounts`,eyebrow:a?.listing===!1?`Search results`:`Recently active accounts`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>e(`/accounts/shared-devices`),children:[(0,W.jsx)(ct,{size:15}),` `,`Shared devices`]}),(0,W.jsxs)(`button`,{className:`btn`,type:`button`,onClick:()=>{y(),S()},disabled:p,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]})]}),children:[h&&(0,W.jsx)(q,{children:h}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Total users`,value:s?String(s.total):`…`}),(0,W.jsx)(Y,{label:`Online now`,value:s?String(s.online):`…`,tone:`good`}),(0,W.jsx)(Y,{label:`Online device records`,value:String(C.devices)})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),y()},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:t,onChange:e=>n(e.target.value),placeholder:`User ID / phone / username / email / name`})]}),(0,W.jsxs)(`label`,{className:`gift-page-size`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsxs)(`select`,{value:String(r),onChange:e=>i(Number(e.target.value)),children:[(0,W.jsx)(`option`,{value:`10`,children:`10`}),(0,W.jsx)(`option`,{value:`20`,children:`20`}),(0,W.jsx)(`option`,{value:`50`,children:`50`}),(0,W.jsx)(`option`,{value:`100`,children:`100`})]})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:p,children:[p?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>void x(),disabled:!w,children:[(0,W.jsx)(xe,{size:15}),` `,`Previous page`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>void b(),disabled:!T,children:[(0,W.jsx)(Se,{size:15}),` `,`Next page`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{className:`avatar-col`}),(0,W.jsx)(`th`,{children:`User ID`}),(0,W.jsx)(`th`,{children:`Phone`}),(0,W.jsx)(`th`,{children:`Username`}),(0,W.jsx)(`th`,{children:`Name`}),(0,W.jsx)(`th`,{children:`Login email`}),(0,W.jsx)(`th`,{children:`Device`}),(0,W.jsx)(`th`,{children:`Last active`}),(0,W.jsx)(`th`,{children:`Premium`}),(0,W.jsx)(`th`,{children:`Verified`}),(0,W.jsx)(`th`,{children:`Frozen`}),(0,W.jsx)(`th`,{children:`Updated`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[a?.rows.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`avatar-col`,children:(0,W.jsx)(`button`,{className:`avatar-link`,type:`button`,onClick:()=>e(`/accounts/${t.ID}`),"aria-label":`Open account ${t.ID}`,children:(0,W.jsx)(En,{id:t.ID,firstName:t.FirstName,lastName:t.LastName,username:t.Username})})}),(0,W.jsx)(`td`,{className:`mono`,children:t.ID}),(0,W.jsx)(`td`,{children:xt(t.Phone)}),(0,W.jsx)(`td`,{children:(0,W.jsx)(Ut,{username:t.Username,collectibles:t.Collectibles})}),(0,W.jsx)(`td`,{children:St(t)}),(0,W.jsx)(`td`,{children:t.LoginEmail||(0,W.jsx)(`span`,{className:`muted-cell`,children:`None`})}),(0,W.jsx)(`td`,{children:t.DeviceCount}),(0,W.jsx)(`td`,{children:U(t.LastActiveAt)}),(0,W.jsx)(`td`,{children:t.PremiumUntil>0?(0,W.jsxs)(J,{tone:`good`,children:[`Premium`,` `,wt(t.PremiumUntil)]}):(0,W.jsx)(J,{children:`None`})}),(0,W.jsxs)(`td`,{children:[t.Verified?(0,W.jsx)(J,{tone:`good`,children:`Verified`}):(0,W.jsx)(J,{children:`Not verified`}),` `,(0,W.jsx)(On,{scam:t.Scam,fake:t.Fake})]}),(0,W.jsx)(`td`,{children:t.Frozen?(0,W.jsx)(J,{tone:`danger`,children:`Frozen`}):(0,W.jsx)(J,{children:`Normal`})}),(0,W.jsx)(`td`,{children:U(t.UpdatedAt)}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,onClick:()=>e(`/accounts/${t.ID}`),children:[`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),(!a||a.rows.length===0)&&(0,W.jsx)(Bt,{colSpan:12})]})]})})]})}function Wn({navigate:e}){let[t,n]=(0,g.useState)([]),[r,i]=(0,g.useState)(!1),[a,o]=(0,g.useState)(0),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(``);async function d(e=!1){c(!0),u(``);let t=new URLSearchParams({limit:`20`,offset:String(e?a:0)});try{let r=await k.sharedDeviceGroups(t),a=r.rows??[];n(t=>e?[...t,...a]:a),o(r.next_offset),i(!!r.has_more)}catch(e){u(O(e))}finally{c(!1)}}(0,g.useEffect)(()=>{d(!1)},[]);let f=t.reduce((e,t)=>e+t.AccountCount,0);return(0,W.jsxs)(G,{title:`Shared Devices`,eyebrow:`Multi-account signal — device/IP overlap across different accounts`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>e(`/accounts`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to accounts`]}),(0,W.jsxs)(`button`,{className:`btn`,type:`button`,onClick:()=>d(!1),disabled:s,children:[(0,W.jsx)(B,{size:15,className:s?`spin`:``}),` `,`Refresh`]})]}),children:[l&&(0,W.jsx)(q,{children:l}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Device groups on page`,value:String(t.length)}),(0,W.jsx)(Y,{label:`Accounts flagged on page`,value:String(f),tone:`warn`})]}),(0,W.jsxs)(`p`,{className:`about-text`,children:[`Each card below is a device fingerprint (device model + OS + platform + IP) that more than one account has authorized from. `,`device_model/system_version are self-reported by the client, and IP alone can collide innocently -- use this as a lead, not a verdict.`]}),(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[t.map(t=>(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:t.DeviceModel||`Unknown device`,text:`${t.Platform||`unknown platform`} ${t.SystemVersion} · ${t.IP} · last active ${U(t.LastActiveAt)}`,action:(0,W.jsxs)(J,{tone:`warn`,children:[(0,W.jsx)(ct,{size:12}),` `,`${t.AccountCount} accounts`]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{className:`avatar-col`}),(0,W.jsx)(`th`,{children:`User ID`}),(0,W.jsx)(`th`,{children:`Phone`}),(0,W.jsx)(`th`,{children:`Username`}),(0,W.jsx)(`th`,{children:`Name`}),(0,W.jsx)(`th`,{children:`Active from this device`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsx)(`tbody`,{children:t.Accounts.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`avatar-col`,children:(0,W.jsx)(`button`,{className:`avatar-link`,type:`button`,onClick:()=>e(`/accounts/${t.UserID}`),"aria-label":`Open account ${t.UserID}`,children:(0,W.jsx)(En,{id:t.UserID,firstName:t.FirstName,lastName:t.LastName,username:t.Username})})}),(0,W.jsx)(`td`,{className:`mono`,children:t.UserID}),(0,W.jsx)(`td`,{children:xt(t.Phone)}),(0,W.jsx)(`td`,{children:H(t.Username)||`-`}),(0,W.jsx)(`td`,{children:St(t)||`-`}),(0,W.jsx)(`td`,{children:U(t.ActiveAt)}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,onClick:()=>e(`/accounts/${t.UserID}`),children:[`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.UserID))})]})})]},`${t.DeviceModel}|${t.SystemVersion}|${t.Platform}|${t.IP}`)),t.length===0&&(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsx)(`table`,{className:`data-table`,children:(0,W.jsx)(`tbody`,{children:(0,W.jsx)(Bt,{colSpan:7})})})})]}),r&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>d(!0),disabled:s,children:[s?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})})]})}function Gn({label:e,value:t,onChange:n}){let[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)([]),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(``);async function d(){c(!0),u(``);let e=new URLSearchParams({limit:`20`});r.trim()&&e.set(`q`,r.trim());try{o((await k.accounts(e)).rows)}catch(e){u(O(e))}finally{c(!1)}}return(0,g.useEffect)(()=>{d()},[]),(0,W.jsxs)(`div`,{className:`entity-picker`,children:[(0,W.jsxs)(`div`,{className:`picker-head`,children:[(0,W.jsx)(`span`,{children:e}),t?(0,W.jsxs)(`button`,{className:`link-button`,type:`button`,onClick:()=>n(null),children:[(0,W.jsx)(yt,{size:13}),` `,`Clear`]}):null]}),t?(0,W.jsxs)(`div`,{className:`selected-entity`,children:[(0,W.jsx)(ye,{size:15}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`strong`,{children:St(t)}),(0,W.jsx)(`span`,{className:`mono`,children:t.ID})]}),(0,W.jsx)(`span`,{children:H(t.Username)||xt(t.Phone)||`-`})]}):null,(0,W.jsxs)(`div`,{className:`picker-search`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),d())},placeholder:`Search user_id / phone / username`}),(0,W.jsx)(`button`,{className:`btn compact-btn`,type:`button`,onClick:d,disabled:s,children:s?(0,W.jsx)(I,{size:14,className:`spin`}):`Search`})]}),l&&(0,W.jsx)(`div`,{className:`picker-error`,children:l}),(0,W.jsxs)(`div`,{className:`picker-results`,children:[a.map(e=>(0,W.jsxs)(`button`,{className:`picker-row ${t?.ID===e.ID?`selected`:``}`,type:`button`,onClick:()=>n(e),children:[(0,W.jsx)(`span`,{className:`mono`,children:e.ID}),(0,W.jsx)(`strong`,{children:St(e)}),(0,W.jsx)(`span`,{children:H(e.Username)||xt(e.Phone)||`-`}),e.Verified?(0,W.jsx)(J,{tone:`good`,children:`Verified`}):(0,W.jsx)(J,{children:`Regular`})]},e.ID)),a.length===0&&!s?(0,W.jsx)(`div`,{className:`picker-empty`,children:`No results`}):null]})]})}function Kn({label:e,selected:t,onChange:n}){let[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)([]),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(``);async function d(){c(!0),u(``);let e=new URLSearchParams({limit:`20`});r.trim()&&e.set(`q`,r.trim());try{o((await k.accounts(e)).rows)}catch(e){u(O(e))}finally{c(!1)}}(0,g.useEffect)(()=>{d()},[]);function f(e){t.some(t=>t.ID===e.ID)?n(t.filter(t=>t.ID!==e.ID)):n([...t,e])}function p(e){n(t.filter(t=>t.ID!==e))}return(0,W.jsxs)(`div`,{className:`entity-picker`,children:[(0,W.jsxs)(`div`,{className:`picker-head`,children:[(0,W.jsx)(`span`,{children:e}),t.length>0?(0,W.jsxs)(`button`,{className:`link-button`,type:`button`,onClick:()=>n([]),children:[(0,W.jsx)(yt,{size:13}),` `,`Clear all`]}):null]}),t.length>0?(0,W.jsx)(`div`,{className:`picker-chip-list`,children:t.map(e=>(0,W.jsxs)(`span`,{className:`picker-chip`,children:[St(e),` `,(0,W.jsx)(`span`,{className:`mono`,children:e.ID}),(0,W.jsx)(`button`,{type:`button`,onClick:()=>p(e.ID),"aria-label":`Remove ${e.ID}`,children:(0,W.jsx)(yt,{size:12})})]},e.ID))}):null,(0,W.jsxs)(`div`,{className:`picker-search`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),d())},placeholder:`Search user_id / phone / username`}),(0,W.jsx)(`button`,{className:`btn compact-btn`,type:`button`,onClick:d,disabled:s,children:s?(0,W.jsx)(I,{size:14,className:`spin`}):`Search`})]}),l&&(0,W.jsx)(`div`,{className:`picker-error`,children:l}),(0,W.jsxs)(`div`,{className:`picker-results`,children:[a.map(e=>{let n=t.some(t=>t.ID===e.ID);return(0,W.jsxs)(`button`,{className:`picker-row ${n?`selected`:``}`,type:`button`,onClick:()=>f(e),children:[(0,W.jsx)(`span`,{className:`mono`,children:e.ID}),(0,W.jsx)(`strong`,{children:St(e)}),(0,W.jsx)(`span`,{children:H(e.Username)||xt(e.Phone)||`-`}),n?(0,W.jsx)(ye,{size:15}):null]},e.ID)}),a.length===0&&!s?(0,W.jsx)(`div`,{className:`picker-empty`,children:`No results`}):null]})]})}function qn({label:e,value:t,onChange:n}){let[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)([]),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(``);async function d(){c(!0),u(``);let e=new URLSearchParams({limit:`20`});r.trim()&&e.set(`q`,r.trim().replace(/^@/,``));try{o((await k.bots(e)).rows??[])}catch(e){u(O(e))}finally{c(!1)}}return(0,g.useEffect)(()=>{d()},[]),(0,W.jsxs)(`div`,{className:`entity-picker`,children:[(0,W.jsxs)(`div`,{className:`picker-head`,children:[(0,W.jsx)(`span`,{children:e}),t?(0,W.jsxs)(`button`,{className:`link-button`,type:`button`,onClick:()=>n(null),children:[(0,W.jsx)(yt,{size:13}),` `,`Clear`]}):null]}),t?(0,W.jsxs)(`div`,{className:`selected-entity`,children:[(0,W.jsx)(ye,{size:15}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`strong`,{children:t.FirstName||`-`}),(0,W.jsx)(`span`,{className:`mono`,children:t.ID})]}),(0,W.jsx)(`span`,{children:H(t.Username)||`-`})]}):null,(0,W.jsxs)(`div`,{className:`picker-search`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),d())},placeholder:`Bot username or id`}),(0,W.jsx)(`button`,{className:`btn compact-btn`,type:`button`,onClick:d,disabled:s,children:s?(0,W.jsx)(I,{size:14,className:`spin`}):`Search`})]}),l&&(0,W.jsx)(`div`,{className:`picker-error`,children:l}),(0,W.jsxs)(`div`,{className:`picker-results`,children:[a.map(e=>(0,W.jsxs)(`button`,{className:`picker-row ${t?.ID===e.ID?`selected`:``}`,type:`button`,onClick:()=>n(e),children:[(0,W.jsx)(`span`,{className:`mono`,children:e.ID}),(0,W.jsx)(`strong`,{children:e.FirstName||`-`}),(0,W.jsx)(`span`,{children:H(e.Username)||`-`}),e.System?(0,W.jsx)(J,{tone:`warn`,children:`System`}):(0,W.jsx)(J,{children:`Regular`})]},e.ID)),a.length===0&&!s?(0,W.jsx)(`div`,{className:`picker-empty`,children:`No results`}):null]})]})}function Jn({label:e,value:t,onChange:n}){let[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)([]),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(``);async function d(){c(!0),u(``);let e=new URLSearchParams({limit:`20`});r.trim()&&e.set(`q`,r.trim());try{o((await k.channels(e)).rows)}catch(e){u(O(e))}finally{c(!1)}}return(0,g.useEffect)(()=>{d()},[]),(0,W.jsxs)(`div`,{className:`entity-picker`,children:[(0,W.jsxs)(`div`,{className:`picker-head`,children:[(0,W.jsx)(`span`,{children:e}),t?(0,W.jsxs)(`button`,{className:`link-button`,type:`button`,onClick:()=>n(null),children:[(0,W.jsx)(yt,{size:13}),` `,`Clear`]}):null]}),t?(0,W.jsxs)(`div`,{className:`selected-entity`,children:[(0,W.jsx)(ye,{size:15}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`strong`,{children:t.Title||`-`}),(0,W.jsx)(`span`,{className:`mono`,children:t.ID})]}),(0,W.jsx)(`span`,{children:H(t.Username)||Ct(t)})]}):null,(0,W.jsxs)(`div`,{className:`picker-search`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),d())},placeholder:`Search channel_id / username / title`}),(0,W.jsx)(`button`,{className:`btn compact-btn`,type:`button`,onClick:d,disabled:s,children:s?(0,W.jsx)(I,{size:14,className:`spin`}):`Search`})]}),l&&(0,W.jsx)(`div`,{className:`picker-error`,children:l}),(0,W.jsxs)(`div`,{className:`picker-results`,children:[a.map(e=>(0,W.jsxs)(`button`,{className:`picker-row ${t?.ID===e.ID?`selected`:``}`,type:`button`,onClick:()=>n(e),children:[(0,W.jsx)(`span`,{className:`mono`,children:e.ID}),(0,W.jsx)(`strong`,{children:e.Title||`-`}),(0,W.jsx)(`span`,{children:H(e.Username)||Ct(e)}),e.Verified?(0,W.jsx)(J,{tone:`good`,children:`Verified`}):(0,W.jsx)(J,{children:Ct(e)})]},e.ID)),a.length===0&&!s?(0,W.jsx)(`div`,{className:`picker-empty`,children:`No results`}):null]})]})}function Yn({onClose:e,onMinted:t}){let[n,r]=(0,g.useState)(`vault`),[i,a]=(0,g.useState)(null),[o,s]=(0,g.useState)(null),[c,l]=(0,g.useState)(``),[u,d]=(0,g.useState)(`XTR`),[f,p]=(0,g.useState)(``),[m,h]=(0,g.useState)(!1),[_,v]=(0,g.useState)(`TON`),[y,b]=(0,g.useState)(``),[x,S]=(0,g.useState)(!1),[C,w]=(0,g.useState)(``),[T,E]=(0,g.useState)(``),[D,O]=(0,g.useState)(``),k=Nt(f,u),A=m?Nt(y,_):`0`,j=k===null,M=m&&A===null,N=c.trim()!==``&&f.trim()!==``&&!j&&!M&&(n===`vault`||(n===`user`?i!==null:o!==null));function P(){let e={username:c.trim().replace(/^@/,``),currency:u,amount:k??`0`};if(n===`user`&&i&&(e.owner_user_id=String(i.ID)),n===`channel`&&o&&(e.owner_channel_id=String(o.ID)),m&&(e.crypto_currency=_,e.crypto_amount=A??`0`),C.trim()&&(e.url=C.trim()),T){let t=Date.parse(`${T}T${D||`00:00`}:00Z`);Number.isFinite(t)&&(e.purchase_date=Math.floor(t/1e3))}return e}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Mint a collectible username`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`NFT usernames`}),(0,W.jsx)(`h2`,{children:`Mint a collectible username`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:e,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsxs)(`div`,{className:`mint-field-group`,children:[(0,W.jsx)(`div`,{className:`mint-field-group-label`,children:`1. Username`}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Username`}),(0,W.jsx)(`input`,{value:c,onChange:e=>l(e.target.value),placeholder:`durov`})]})]}),(0,W.jsxs)(`div`,{className:`mint-field-group`,children:[(0,W.jsx)(`div`,{className:`mint-field-group-label`,children:`2. Owner`}),(0,W.jsxs)(`div`,{className:`toolbar`,role:`group`,"aria-label":`Owner type`,children:[(0,W.jsxs)(`button`,{type:`button`,className:`btn ${n===`vault`?`primary`:``}`,onClick:()=>r(`vault`),children:[(0,W.jsx)(vt,{size:15}),` `,`Vault (no owner)`]}),(0,W.jsx)(`button`,{type:`button`,className:`btn ${n===`user`?`primary`:``}`,onClick:()=>r(`user`),children:`User owner`}),(0,W.jsx)(`button`,{type:`button`,className:`btn ${n===`channel`?`primary`:``}`,onClick:()=>r(`channel`),children:`Channel owner`})]}),n===`user`&&(0,W.jsx)(Gn,{label:`User owner`,value:i,onChange:a}),n===`channel`&&(0,W.jsx)(Jn,{label:`Channel owner`,value:o,onChange:s}),n===`vault`&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Mints the asset unassigned; issue it to someone later from the asset page.`})]}),(0,W.jsxs)(`div`,{className:`mint-field-group`,children:[(0,W.jsx)(`div`,{className:`mint-field-group-label`,children:`3. Price`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`A record of what it was sold for -- minting doesn't charge anyone.`}),(0,W.jsxs)(`div`,{className:`bot-create-fields`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Currency`}),(0,W.jsxs)(`select`,{value:u,onChange:e=>d(e.target.value),children:[(0,W.jsx)(`option`,{value:`XTR`,children:`XTR`}),(0,W.jsx)(`option`,{value:`TON`,children:`TON`}),(0,W.jsx)(`option`,{value:`USD`,children:`USD`})]})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Amount (${u})`}),(0,W.jsx)(`input`,{value:f,onChange:e=>p(e.target.value),inputMode:`decimal`,placeholder:`1000`})]})]}),f.trim()!==``&&!j&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Clients will show: ${Mt(k??`0`,u)}.`}),j&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not a valid ${u} amount: digits only, at most ${String(kt(u))} decimal places.`}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:m,onChange:e=>h(e.target.checked)}),` Also record a TON price`]}),m&&(0,W.jsxs)(`div`,{className:`bot-create-fields`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Crypto currency`}),(0,W.jsx)(`select`,{value:_,onChange:e=>v(e.target.value),children:(0,W.jsx)(`option`,{value:`TON`,children:`TON`})})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Crypto amount (${_})`}),(0,W.jsx)(`input`,{value:y,onChange:e=>b(e.target.value),inputMode:`decimal`,placeholder:`12.5`})]})]}),M&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not a valid ${_} amount: digits only, at most ${String(kt(_))} decimal places.`})]}),(0,W.jsxs)(`div`,{className:`mint-field-group`,children:[(0,W.jsx)(`button`,{type:`button`,className:`link-button`,onClick:()=>S(e=>!e),children:x?`Hide marketplace record`:`+ Add marketplace record (optional)`}),x&&(0,W.jsxs)(`div`,{className:`bot-create-fields`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Marketplace URL`}),(0,W.jsx)(`input`,{value:C,onChange:e=>w(e.target.value),placeholder:`https://fragment.com/username/durov`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Purchase date (UTC)`}),(0,W.jsx)(`input`,{value:T,onChange:e=>E(e.target.value),type:`date`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Purchase time (UTC)`}),(0,W.jsx)(`input`,{value:D,onChange:e=>O(e.target.value),type:`time`,step:60,disabled:!T})]})]})]})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:e,children:`Close`}),(0,W.jsx)(Z,{disabled:!N,label:`Mint username`,icon:(0,W.jsx)(Ye,{size:15}),tone:`neutral`,path:`/api/actions/mint-collectible-username`,payload:P,onDone:t})]})]})}),document.body)}function Xn({navigate:e}){let[t,n]=(0,g.useState)(`all`),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(`50`),[s,c]=(0,g.useState)([]),[l,u]=(0,g.useState)(!1),[d,f]=(0,g.useState)(``),[p,m]=(0,g.useState)(!1),[h,_]=(0,g.useState)(``),[v,y]=(0,g.useState)(!1);async function b(e=!1){m(!0),_(``);let n=new URLSearchParams({limit:a});t!==`all`&&n.set(`status`,t),r.trim()&&n.set(`q`,r.trim().replace(/^@/,``)),e&&d&&n.set(`before_id`,d);try{let t=await k.collectibleUsernames(n),r=t.rows??[];c(t=>e?[...t,...r]:r),f(t.next_before_id??``),u(!!t.has_more)}catch(e){_(O(e))}finally{m(!1)}}(0,g.useEffect)(()=>{b(!1)},[]);let x=s.filter(e=>e.Status===`vault`).length,S=s.filter(e=>e.Status===`owned`).length,C=s.filter(e=>e.Status===`burned`).length;return(0,W.jsxs)(G,{title:`Collectible usernames`,eyebrow:`NFT usernames / Registry`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`button`,onClick:()=>y(!0),children:[(0,W.jsx)(Ye,{size:15}),` `,`Mint username`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>b(!1),disabled:p,children:[(0,W.jsx)(B,{size:15,className:p?`spin`:``}),` `,`Refresh`]})]}),children:[h&&(0,W.jsx)(q,{children:h}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Loaded rows`,value:String(s.length)}),(0,W.jsx)(Y,{label:`In vault`,value:String(x)}),(0,W.jsx)(Y,{label:`Held by owners`,value:String(S),tone:`good`}),(0,W.jsx)(Y,{label:`Burned`,value:String(C),tone:C?`danger`:`neutral`})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),b(!1)},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`Search by username`})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Status`}),(0,W.jsxs)(`select`,{value:t,onChange:e=>n(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All statuses`}),(0,W.jsx)(`option`,{value:`vault`,children:`Vault`}),(0,W.jsx)(`option`,{value:`owned`,children:`Owned`}),(0,W.jsx)(`option`,{value:`burned`,children:`Burned`})]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsx)(`input`,{className:`small-input`,value:a,onChange:e=>o(e.target.value),type:`number`,min:`1`,max:`200`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:p,children:[p?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Username`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Owner`}),(0,W.jsx)(`th`,{children:`Price`}),(0,W.jsx)(`th`,{children:`Purchase date (UTC)`}),(0,W.jsx)(`th`,{children:`Transfers`}),(0,W.jsx)(`th`,{children:`Updated`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[s.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{children:(0,W.jsx)(`strong`,{children:H(t.Username)})}),(0,W.jsx)(`td`,{children:(0,W.jsx)(Zn,{status:t.Status})}),(0,W.jsx)(`td`,{children:Qn(t,`Vault`)}),(0,W.jsx)(`td`,{className:`mono`,children:$n(t)}),(0,W.jsx)(`td`,{children:U(t.PurchaseDate)||`-`}),(0,W.jsx)(`td`,{className:`mono`,children:t.TransferCount}),(0,W.jsx)(`td`,{children:U(t.UpdatedAt)||`-`}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,type:`button`,onClick:()=>e(`/collectible-usernames/${t.ID}`),children:[(0,W.jsx)(me,{size:14}),` `,`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),s.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})}),l&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>b(!0),disabled:p,children:[p?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})}),v&&(0,W.jsx)(Yn,{onClose:()=>y(!1),onMinted:()=>void b(!1)})]})}function Zn({status:e}){return e===`owned`?(0,W.jsx)(J,{tone:`good`,children:`Owned`}):e===`burned`?(0,W.jsxs)(J,{tone:`danger`,children:[(0,W.jsx)(Me,{size:12}),` `,`Burned`]}):(0,W.jsxs)(J,{children:[(0,W.jsx)(vt,{size:12}),` `,`Vault`]})}function Qn(e,t){return!e.OwnerPeerType||e.OwnerPeerID===``||e.OwnerPeerID===`0`?t:`${H(e.OwnerUsername)||e.OwnerName||e.OwnerPeerID} · ${e.OwnerPeerType}:${e.OwnerPeerID}`}function $n(e){let t=Mt(e.Amount,e.Currency);return e.CryptoCurrency&&e.CryptoAmount&&e.CryptoAmount!==`0`?`${Mt(e.CryptoAmount,e.CryptoCurrency)} (${t})`:t}function er({id:e,navigate:t}){let[n,r]=(0,g.useState)(null),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(!1),[c,l]=(0,g.useState)(`profile`),[u,d]=(0,g.useState)(`user`),[f,p]=(0,g.useState)(null),[m,h]=(0,g.useState)(null);async function _(){s(!0),a(``);try{r(await k.collectibleUsername(e))}catch(e){a(O(e))}finally{s(!1)}}if((0,g.useEffect)(()=>{_(),l(`profile`)},[e]),i&&!n)return(0,W.jsx)(q,{children:i});if(!n)return(0,W.jsx)(Vt,{label:o?`Loading collectible username…`:`Waiting for data`});let v=n.asset,y=n.transfers??[],b=`Vault`,x=!!v.OwnerPeerType&&v.OwnerPeerID!==``&&v.OwnerPeerID!==`0`,S=v.Status===`burned`;function C(){x&&t(v.OwnerPeerType===`channel`?`/channels/${v.OwnerPeerID}`:`/accounts/${v.OwnerPeerID}`)}function w(){let e={username:v.Username};return u===`user`&&f&&(e.to_user_id=String(f.ID)),u===`channel`&&m&&(e.to_channel_id=String(m.ID)),e}let T=[{key:`profile`,label:`Profile & Status`,icon:(0,W.jsx)(se,{size:15})},{key:`actions`,label:`Actions & Management`,icon:(0,W.jsx)(nt,{size:15})}];return(0,W.jsxs)(G,{title:`Collectible ${H(v.Username)}`,eyebrow:`NFT usernames / Asset`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/collectible-usernames`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to list`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:_,disabled:o,children:[(0,W.jsx)(B,{size:15,className:o?`spin`:``}),` `,`Refresh`]})]}),children:[i&&(0,W.jsx)(q,{children:i}),(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsx)(`div`,{className:`entity-head-main`,children:(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:H(v.Username)}),(0,W.jsx)(`div`,{className:`entity-subtitle`,children:`Asset #${v.ID}`})]})}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[(0,W.jsx)(Zn,{status:v.Status}),(0,W.jsx)(J,{tone:v.TransferCount>0?`warn`:`neutral`,children:`${v.TransferCount} transfers`}),v.Status===`owned`&&(0,W.jsx)(J,{tone:v.RegistryActive?`good`:`warn`,children:v.RegistryActive?`Active in profile`:`Hidden in profile`})]})]}),(0,W.jsx)(`div`,{className:`toolbar`,role:`group`,"aria-label":`Asset sections`,children:T.map(e=>(0,W.jsxs)(`button`,{className:`btn icon-text ${c===e.key?`primary`:``}`,type:`button`,"aria-pressed":c===e.key,onClick:()=>l(e.key),children:[e.icon,` `,e.label]},e.key))}),c===`profile`&&(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Owner`,value:Qn(v,b)}),(0,W.jsx)(X,{label:`Price`,value:$n(v),mono:!0}),(0,W.jsx)(X,{label:`Purchase date (UTC)`,value:U(v.PurchaseDate)||`-`}),(0,W.jsx)(X,{label:`Original owner`,value:rr(v.OriginalOwnerPeerType,v.OriginalOwnerPeerID,b,v.OriginalOwnerUsername)}),(0,W.jsx)(X,{label:`Transfers`,value:String(v.TransferCount),mono:!0}),(0,W.jsx)(X,{label:`Created`,value:U(v.CreatedAt)||`-`}),(0,W.jsx)(X,{label:`Updated`,value:U(v.UpdatedAt)||`-`})]}),(0,W.jsxs)(`div`,{className:`toolbar`,children:[x&&(0,W.jsx)(`button`,{className:`row-link`,type:`button`,onClick:C,children:v.OwnerPeerType===`channel`?`Open owner channel`:`Open owner account`}),v.URL&&(0,W.jsxs)(`a`,{className:`row-link`,href:v.URL,target:`_blank`,rel:`noreferrer noopener`,children:[(0,W.jsx)(De,{size:14}),` `,`Open marketplace page`]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Provenance history`,text:`Mint, transfer, revoke and burn events in chronological order.`,action:(0,W.jsx)($e,{size:16})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Event`}),(0,W.jsx)(`th`,{children:`From`}),(0,W.jsx)(`th`,{children:`To`}),(0,W.jsx)(`th`,{children:`Price`}),(0,W.jsx)(`th`,{children:`Actor`}),(0,W.jsx)(`th`,{children:`Reason`}),(0,W.jsx)(`th`,{children:`Time`})]})}),(0,W.jsxs)(`tbody`,{children:[y.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:e.ID}),(0,W.jsx)(`td`,{children:(0,W.jsx)(nr,{kind:e.Kind})}),(0,W.jsx)(`td`,{className:`mono`,children:rr(e.FromPeerType,e.FromPeerID,b,e.FromUsername)}),(0,W.jsx)(`td`,{className:`mono`,children:rr(e.ToPeerType,e.ToPeerID,b,e.ToUsername)}),(0,W.jsx)(`td`,{className:`mono`,children:e.Amount&&e.Amount!==`0`?Mt(e.Amount,e.Currency):`-`}),(0,W.jsx)(`td`,{children:e.Actor||`-`}),(0,W.jsx)(`td`,{className:`truncate`,children:e.Reason||`-`}),(0,W.jsx)(`td`,{children:U(e.CreatedAt)||`-`})]},e.ID)),y.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})})]})]}),c===`actions`&&(0,W.jsx)(`div`,{className:`stacked-sections`,children:S?(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Asset Operations`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This username is burned — no further operations are possible.`})})]}):(0,W.jsxs)(`div`,{className:`action-groups`,children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Transfer Ownership`,text:`Sent immediately; appended to the provenance history.`}),(0,W.jsxs)(`div`,{className:`card-body`,children:[(0,W.jsxs)(`div`,{className:`toolbar`,role:`group`,"aria-label":`Recipient type`,children:[(0,W.jsx)(`button`,{type:`button`,className:`btn ${u===`user`?`primary`:``}`,onClick:()=>d(`user`),children:`To user`}),(0,W.jsx)(`button`,{type:`button`,className:`btn ${u===`channel`?`primary`:``}`,onClick:()=>d(`channel`),children:`To channel`})]}),u===`user`?(0,W.jsx)(Gn,{label:`To user`,value:f,onChange:p}):(0,W.jsx)(Jn,{label:`To channel`,value:m,onChange:h}),(0,W.jsx)(Z,{label:`Transfer`,icon:(0,W.jsx)(de,{size:15}),tone:`warn`,path:`/api/actions/transfer-collectible-username`,payload:w,onDone:_})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Revoke To Vault`,text:`Returns the username to the vault; it can be issued again later.`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(`div`,{className:`action-stack`,children:(0,W.jsx)(Z,{label:`Revoke to vault`,icon:(0,W.jsx)(mt,{size:15}),tone:`warn`,path:`/api/actions/revoke-collectible-username`,payload:()=>({username:v.Username,burn:!1}),onDone:_})})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Danger Zone`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsxs)(`div`,{className:`danger-zone`,children:[(0,W.jsx)(Z,{label:`Burn permanently`,icon:(0,W.jsx)(Me,{size:15}),tone:`danger`,path:`/api/actions/revoke-collectible-username`,payload:()=>({username:v.Username,burn:!0}),onDone:_}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Irreversible: the username is destroyed and can never be issued again.`}),(0,W.jsx)(Z,{label:`Delete record`,icon:(0,W.jsx)(pt,{size:15}),tone:`danger`,path:`/api/actions/delete-collectible-username`,payload:()=>({username:v.Username}),onDone:()=>t(`/collectible-usernames`)}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Erases the asset and its ownership history, and frees the username for a fresh issue. Use this for a username issued by mistake; a burn keeps the history instead.`})]})})]})]})})]})}var tr={mint:`Mint`,transfer:`Transfer`,burn:`Burn`,revoke:`Revoke`};function nr({kind:e}){return(0,W.jsx)(J,{tone:e===`burn`?`danger`:e===`revoke`?`warn`:e===`mint`?`good`:`neutral`,children:tr[e]})}function rr(e,t,n,r=``){if(!e||t===``||t===`0`)return n;let i=H(r);return i?`${i} · ${e}:${t}`:`${e}:${t}`}function ir({id:e,navigate:t}){let[n,r]=(0,g.useState)(null),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(!1),[c,l]=(0,g.useState)(`profile`),[u,d]=(0,g.useState)(!1),[f,p]=(0,g.useState)(0);async function m(){s(!0),a(``);try{r(await k.channel(e))}catch(e){a(O(e))}finally{s(!1)}}if((0,g.useEffect)(()=>{m(),l(`profile`)},[e]),i)return(0,W.jsx)(q,{children:i});if(!n)return(0,W.jsx)(Vt,{label:o?`Loading channel detail`:`Waiting for data`});let h=n.Channel,_=[{key:`profile`,label:`Profile & Status`,icon:(0,W.jsx)(se,{size:15})},{key:`actions`,label:`Actions & Management`,icon:(0,W.jsx)(nt,{size:15})}];return(0,W.jsxs)(G,{title:`${Ct(h)} #${h.ID}`,eyebrow:`Channel Profile`,actions:(0,W.jsxs)(`button`,{className:`btn icon-text`,onClick:()=>t(`/channels`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to list`]}),children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{className:`entity-head-main`,children:[(0,W.jsxs)(`div`,{className:`avatar-edit-slot`,children:[(0,W.jsx)(En,{id:h.ID,kind:`channel`,title:h.Title,size:64,refreshKey:f||void 0}),(0,W.jsx)(`button`,{className:`icon-btn avatar-edit-btn`,type:`button`,"aria-label":`Change avatar`,title:`Change avatar`,onClick:()=>d(!0),children:(0,W.jsx)(Le,{size:13})})]}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:h.Title||`-`}),(0,W.jsxs)(`div`,{className:`entity-subtitle`,children:[H(h.Username)||`No username`,` · `,`Creator ${h.CreatorUserID}`]})]})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[(0,W.jsx)(J,{children:Ct(h)}),h.Verified?(0,W.jsx)(J,{tone:`good`,children:`Verified`}):(0,W.jsx)(J,{children:`Not verified`}),(0,W.jsx)(On,{scam:h.Scam,fake:h.Fake}),h.Deleted?(0,W.jsx)(J,{tone:`danger`,children:`Deleted`}):(0,W.jsx)(J,{children:`Valid`})]})]}),(0,W.jsx)(`div`,{className:`toolbar`,role:`group`,"aria-label":`Channel sections`,children:_.map(e=>(0,W.jsxs)(`button`,{className:`btn icon-text ${c===e.key?`primary`:``}`,type:`button`,"aria-pressed":c===e.key,onClick:()=>l(e.key),children:[e.icon,` `,e.label]},e.key))}),c===`profile`&&(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Channel ID`,value:String(h.ID),mono:!0}),(0,W.jsx)(X,{label:`access_hash`,value:String(h.AccessHash),mono:!0}),(0,W.jsx)(X,{label:`Members`,value:`${h.ParticipantsCount} / Admins ${h.AdminsCount}`}),(0,W.jsx)(X,{label:`Moderation`,value:`Banned ${h.BannedCount} / Kicked ${h.KickedCount}`}),(0,W.jsx)(X,{label:`Channel flags`,value:`broadcast=${h.Broadcast} megagroup=${h.Megagroup} forum=${h.Forum}`}),(0,W.jsx)(X,{label:`top / pinned / PTS`,value:`${h.TopMessageID} / ${h.PinnedMessageID} / ${h.PTS}`}),(0,W.jsx)(X,{label:`Created`,value:wt(h.Date)||`-`}),(0,W.jsx)(X,{label:`Updated`,value:U(h.UpdatedAt)||`-`})]}),h.About&&(0,W.jsx)(`p`,{className:`about-text`,children:h.About}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Channel Raw Row`,text:`Database read-only snapshot`}),(0,W.jsx)(Ht,{value:n.ChannelJSON})]})]}),c===`actions`&&(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`action-groups`,children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Verification & Moderation Flags`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsxs)(`div`,{className:`action-stack`,children:[(0,W.jsx)(Z,{label:h.Verified?`Clear verified`:`Set verified`,icon:(0,W.jsx)(ee,{size:15}),tone:`warn`,path:`/api/actions/set-channel-verified`,payload:()=>({channel_id:h.ID,verified:!h.Verified}),onDone:m}),(0,W.jsx)(kn,{idKey:`channel_id`,id:h.ID,path:`/api/actions/set-channel-flags`,scam:h.Scam,fake:h.Fake,onDone:m})]})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Settings`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(Ln,{channel:h,onDone:m})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Username`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(jn,{idKey:`channel_id`,id:h.ID,path:`/api/actions/set-channel-username`,current:h.Username,onDone:m})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Profile Color`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(Fn,{idKey:`channel_id`,id:h.ID,path:`/api/actions/set-channel-color`,onDone:m})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Emoji Status`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(In,{idKey:`channel_id`,id:h.ID,path:`/api/actions/set-channel-emoji-status`,onDone:m})})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Recent Admin Actions`,text:`Last 30 audit rows`,action:(0,W.jsx)($e,{size:16})}),(0,W.jsx)(zt,{rows:n.AuditLogs})]})]}),u&&(0,W.jsx)(xn,{kind:`channel`,id:h.ID,onClose:()=>d(!1),onDone:()=>{p(e=>e+1),m()}})]})}var ar={beforeID:0,beforeUpdatedUS:0};function or({navigate:e}){let[t,n]=(0,g.useState)(``),[r,i]=(0,g.useState)(50),[a,o]=(0,g.useState)(null),[s,c]=(0,g.useState)([]),[l,u]=(0,g.useState)(ar),[d,f]=(0,g.useState)(!1),[p,m]=(0,g.useState)(``);async function h(e,t){f(!0),m(``);let n=new URLSearchParams({limit:String(r)});e.trim()&&n.set(`q`,e.trim()),(t.beforeID||t.beforeUpdatedUS)&&(n.set(`before_id`,String(t.beforeID)),n.set(`before_updated_us`,String(t.beforeUpdatedUS)));try{let e=await k.channels(n);return o(e),e}catch(e){return m(O(e)),null}finally{f(!1)}}async function _(){c([]),u(ar),await h(t,ar)}async function v(){if(!a?.has_more)return;let e={beforeID:a.next_before_id,beforeUpdatedUS:a.next_before_updated_us};await h(t,e)&&(c(e=>[...e,l]),u(e))}async function y(){if(s.length===0)return;let e=s[s.length-1];await h(t,e)&&(c(e=>e.slice(0,-1)),u(e))}(0,g.useEffect)(()=>{_()},[]);let b=Vn(a?.rows??[]),x=s.length>0&&!d,S=!!a?.has_more&&!d;return(0,W.jsxs)(G,{title:`Supergroups and Channels`,eyebrow:a?.listing===!1?`Search results`:`Recently updated`,actions:(0,W.jsxs)(`button`,{className:`btn`,type:`button`,onClick:()=>void _(),disabled:d,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]}),children:[p&&(0,W.jsx)(q,{children:p}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Entities on page`,value:String(a?.rows.length??0)}),(0,W.jsx)(Y,{label:`Supergroups`,value:String(b.megagroups)}),(0,W.jsx)(Y,{label:`Channels`,value:String(b.broadcasts)}),(0,W.jsx)(Y,{label:`Verified`,value:String(b.verified),tone:`good`})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),_()},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:t,onChange:e=>n(e.target.value),placeholder:`Channel ID / username / title`})]}),(0,W.jsxs)(`label`,{className:`gift-page-size`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsxs)(`select`,{value:String(r),onChange:e=>i(Number(e.target.value)),children:[(0,W.jsx)(`option`,{value:`10`,children:`10`}),(0,W.jsx)(`option`,{value:`20`,children:`20`}),(0,W.jsx)(`option`,{value:`50`,children:`50`}),(0,W.jsx)(`option`,{value:`100`,children:`100`})]})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:d,children:[d?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>void y(),disabled:!x,children:[(0,W.jsx)(xe,{size:15}),` `,`Previous page`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>void v(),disabled:!S,children:[(0,W.jsx)(Se,{size:15}),` `,`Next page`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{className:`avatar-col`}),(0,W.jsx)(`th`,{children:`Channel ID`}),(0,W.jsx)(`th`,{children:`Kind`}),(0,W.jsx)(`th`,{children:`Username`}),(0,W.jsx)(`th`,{children:`Title`}),(0,W.jsx)(`th`,{children:`Members`}),(0,W.jsx)(`th`,{children:`Admins`}),(0,W.jsx)(`th`,{children:`PTS`}),(0,W.jsx)(`th`,{children:`Verified`}),(0,W.jsx)(`th`,{children:`Updated`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[a?.rows.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`avatar-col`,children:(0,W.jsx)(`button`,{className:`avatar-link`,type:`button`,onClick:()=>e(`/channels/${t.ID}`),"aria-label":`Open channel ${t.ID}`,children:(0,W.jsx)(En,{id:t.ID,kind:`channel`,title:t.Title})})}),(0,W.jsx)(`td`,{className:`mono`,children:t.ID}),(0,W.jsx)(`td`,{children:Ct(t)}),(0,W.jsx)(`td`,{children:H(t.Username)}),(0,W.jsx)(`td`,{children:t.Title}),(0,W.jsx)(`td`,{children:t.ParticipantsCount}),(0,W.jsx)(`td`,{children:t.AdminsCount}),(0,W.jsx)(`td`,{children:t.PTS}),(0,W.jsxs)(`td`,{children:[t.Verified?(0,W.jsx)(J,{tone:`good`,children:`Verified`}):(0,W.jsx)(J,{children:`Not verified`}),` `,(0,W.jsx)(On,{scam:t.Scam,fake:t.Fake})]}),(0,W.jsx)(`td`,{children:U(t.UpdatedAt)}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,onClick:()=>e(`/channels/${t.ID}`),children:[`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),(!a||a.rows.length===0)&&(0,W.jsx)(Bt,{colSpan:11})]})]})})]})}function sr({botID:e,onClose:t}){let[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(!1),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(!1);async function u(){if(!n.trim()){s(`Please enter an operation reason`);return}a(!0),s(``),l(!1);try{let t=await k.action(`/api/actions/export-bot-token`,{command_id:``,reason:n.trim(),confirm:!0,bot_user_id:e}),r=t.details?.token;if(t.error||typeof r!=`string`||!r){s(t.error||`No token returned.`);return}await pn(r),l(!0)}catch(e){s(O(e))}finally{a(!1)}}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Copy bot token`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Bot`}),(0,W.jsx)(`h2`,{children:`Copy bot token`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:t,disabled:i,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsx)(`p`,{children:`The token is written straight to your clipboard and is never shown on screen. Paste it wherever it's needed right after copying.`}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsx)(`span`,{children:`Operation reason`}),(0,W.jsx)(`textarea`,{value:n,onChange:e=>r(e.target.value),rows:3,placeholder:`Describe why this token is being retrieved`})]}),o&&(0,W.jsx)(q,{children:o}),c&&(0,W.jsx)(`div`,{className:`secret-reveal`,children:(0,W.jsxs)(`div`,{className:`secret-reveal-label`,children:[(0,W.jsx)(ye,{size:14}),` `,`Token copied to clipboard.`]})})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:t,disabled:i,children:`Close`}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`button`,onClick:()=>void u(),disabled:i,children:[(0,W.jsx)(R,{size:15}),` `,c?`Copy again`:`Copy token`]})]})]})}),document.body)}function cr({id:e,navigate:t}){let[n,r]=(0,g.useState)(null),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(!1),[c,l]=(0,g.useState)(`profile`),[u,d]=(0,g.useState)(!1),[f,p]=(0,g.useState)(0),[m,h]=(0,g.useState)(!1);async function _(){s(!0),a(``);try{r(await k.bot(e))}catch(e){a(O(e))}finally{s(!1)}}if((0,g.useEffect)(()=>{_(),l(`profile`)},[e]),i)return(0,W.jsx)(q,{children:i});if(!n)return(0,W.jsx)(Vt,{label:o?`Loading bot detail`:`Waiting for data`});let v=n.Bot,y=[{key:`profile`,label:`Profile & Status`,icon:(0,W.jsx)(se,{size:15})},{key:`actions`,label:`Actions & Management`,icon:(0,W.jsx)(nt,{size:15})}];return(0,W.jsxs)(G,{title:`Bot #${v.ID}`,eyebrow:`Bot Profile`,actions:(0,W.jsxs)(`button`,{className:`btn icon-text`,onClick:()=>t(`/bots`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to list`]}),children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{className:`entity-head-main`,children:[(0,W.jsxs)(`div`,{className:`avatar-edit-slot`,children:[(0,W.jsx)(En,{id:v.ID,firstName:v.FirstName,username:v.Username,size:64,refreshKey:f||void 0}),(0,W.jsx)(`button`,{className:`icon-btn avatar-edit-btn`,type:`button`,"aria-label":`Change avatar`,title:`Change avatar`,onClick:()=>d(!0),children:(0,W.jsx)(Le,{size:13})})]}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:v.FirstName||`Unnamed bot`}),(0,W.jsx)(`div`,{className:`entity-subtitle`,children:H(v.Username)||`No username`})]})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[(0,W.jsx)(J,{tone:v.System?`warn`:`neutral`,children:v.System?`System`:`User`}),v.Verified?(0,W.jsx)(J,{tone:`good`,children:`Verified`}):(0,W.jsx)(J,{children:`Not verified`}),(0,W.jsx)(On,{scam:v.Scam,fake:v.Fake})]})]}),(0,W.jsx)(`div`,{className:`toolbar`,role:`group`,"aria-label":`Bot sections`,children:y.map(e=>(0,W.jsxs)(`button`,{className:`btn icon-text ${c===e.key?`primary`:``}`,type:`button`,"aria-pressed":c===e.key,onClick:()=>l(e.key),children:[e.icon,` `,e.label]},e.key))}),c===`profile`&&(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Bot ID`,value:String(v.ID),mono:!0}),(0,W.jsx)(X,{label:`Owner`,value:v.OwnerUserID>0?`${v.OwnerUserID} ${H(n.OwnerUsername)}`.trim():`None`}),(0,W.jsx)(X,{label:`Type`,value:v.System?`System`:`User`}),(0,W.jsx)(X,{label:`Updated`,value:U(v.UpdatedAt)||`-`}),(0,W.jsx)(X,{label:`Created`,value:U(v.CreatedAt)||`-`})]}),n.About&&(0,W.jsx)(`p`,{className:`about-text`,children:n.About}),n.Description&&n.Description.trim()!==n.About.trim()&&(0,W.jsx)(`p`,{className:`about-text`,children:n.Description})]}),c===`actions`&&(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`action-groups`,children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Verification & Moderation Flags`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsxs)(`div`,{className:`action-stack`,children:[(0,W.jsx)(Z,{label:v.Verified?`Clear verified`:`Set verified`,icon:(0,W.jsx)(ee,{size:15}),tone:`neutral`,path:`/api/actions/set-verified`,payload:()=>({user_id:v.ID,verified:!v.Verified}),onDone:_}),(0,W.jsx)(kn,{idKey:`user_id`,id:v.ID,path:`/api/actions/set-account-flags`,scam:v.Scam,fake:v.Fake,onDone:_})]})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Username`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(jn,{idKey:`user_id`,id:v.ID,path:`/api/actions/set-account-username`,current:v.Username,onDone:_})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Profile Color`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(Fn,{idKey:`user_id`,id:v.ID,path:`/api/actions/set-account-color`,onDone:_})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Emoji Status`}),(0,W.jsx)(`div`,{className:`card-body`,children:(0,W.jsx)(In,{idKey:`user_id`,id:v.ID,path:`/api/actions/set-account-emoji-status`,onDone:_})})]}),!v.System&&(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Credentials`}),(0,W.jsxs)(`div`,{className:`card-body`,children:[(0,W.jsx)(`div`,{className:`action-stack`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>h(!0),children:[(0,W.jsx)(R,{size:15}),` `,`Copy token`]})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Copies straight to the clipboard through a dedicated confirmation step -- the token itself is never shown on this page.`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Danger Zone`}),(0,W.jsx)(`div`,{className:`card-body`,children:v.System?(0,W.jsx)(`p`,{className:`bot-create-note`,children:`System bots are built in and cannot be deleted.`}):(0,W.jsxs)(`div`,{className:`danger-zone`,children:[(0,W.jsx)(Z,{label:`Delete bot`,icon:(0,W.jsx)(pt,{size:15}),tone:`danger`,path:`/api/actions/delete-bot`,payload:()=>({bot_user_id:v.ID}),onDone:()=>t(`/bots`)}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Permanently deletes this user-created bot and invalidates its token. This cannot be undone.`})]})})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Recent Admin Actions`,text:`Last 30 audit rows`,action:(0,W.jsx)($e,{size:16})}),(0,W.jsx)(zt,{rows:n.AuditLogs})]})]}),u&&(0,W.jsx)(xn,{kind:`user`,id:v.ID,onClose:()=>d(!1),onDone:()=>{p(e=>e+1),_()}}),m&&(0,W.jsx)(sr,{botID:v.ID,onClose:()=>h(!1)})]})}function lr({onClose:e,onCreated:t}){let[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(``);return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Create bot`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Bots`}),(0,W.jsx)(`h2`,{children:`Create bot`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:e,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsx)(`p`,{children:`Provision a bot account owned by the given user. The token is shown once after confirmation.`}),(0,W.jsxs)(`div`,{className:`bot-create-fields`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Owner user ID`}),(0,W.jsx)(`input`,{value:n,onChange:e=>r(e.target.value),type:`number`,min:`1`,placeholder:`123456789`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Display name`}),(0,W.jsx)(`input`,{value:i,onChange:e=>a(e.target.value),placeholder:`e.g. Service Bot`,maxLength:64})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Username`}),(0,W.jsx)(`input`,{value:o,onChange:e=>s(e.target.value),placeholder:`my_service_bot`})]})]}),(0,W.jsx)(`span`,{className:`bot-create-note`,children:`Username must be 5-32 characters and end with 'bot'.`})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:e,children:`Close`}),(0,W.jsx)(Z,{label:`Create bot`,icon:(0,W.jsx)(Ye,{size:15}),tone:`neutral`,path:`/api/actions/create-bot`,payload:()=>({owner_user_id:Et(n),name:i.trim(),username:o.trim().replace(/^@/,``)}),secretField:`token`,onDone:t})]})]})}),document.body)}var ur={beforeID:0};function dr({navigate:e}){let[t,n]=(0,g.useState)(``),[r,i]=(0,g.useState)(50),[a,o]=(0,g.useState)(null),[s,c]=(0,g.useState)([]),[l,u]=(0,g.useState)(ur),[d,f]=(0,g.useState)(!1),[p,m]=(0,g.useState)(``),[h,_]=(0,g.useState)(!1);async function v(e,t){f(!0),m(``);let n=new URLSearchParams({limit:String(r)});e.trim()&&n.set(`q`,e.trim()),t.beforeID&&n.set(`before_id`,String(t.beforeID));try{let e=await k.bots(n);return o(e),e}catch(e){return m(O(e)),null}finally{f(!1)}}async function y(){c([]),u(ur),await v(t,ur)}async function b(){if(!a?.has_more)return;let e={beforeID:a.next_before_id};await v(t,e)&&(c(e=>[...e,l]),u(e))}async function x(){if(s.length===0)return;let e=s[s.length-1];await v(t,e)&&(c(e=>e.slice(0,-1)),u(e))}(0,g.useEffect)(()=>{y()},[]);let S=a?.rows??[],C=S.filter(e=>e.Verified).length,w=S.filter(e=>e.System).length,T=s.length>0&&!d,E=!!a?.has_more&&!d;return(0,W.jsxs)(G,{title:`Bots`,eyebrow:a?.listing===!1?`Search results`:`Recently created bots`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`button`,onClick:()=>_(!0),children:[(0,W.jsx)(Ye,{size:15}),` `,`Create bot`]}),(0,W.jsxs)(`button`,{className:`btn`,type:`button`,onClick:()=>void y(),disabled:d,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]})]}),children:[p&&(0,W.jsx)(q,{children:p}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Bots on page`,value:String(S.length)}),(0,W.jsx)(Y,{label:`Verified`,value:String(C),tone:`good`}),(0,W.jsx)(Y,{label:`System`,value:String(w)})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),y()},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:t,onChange:e=>n(e.target.value),placeholder:`Bot ID / username`})]}),(0,W.jsxs)(`label`,{className:`gift-page-size`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsxs)(`select`,{value:String(r),onChange:e=>i(Number(e.target.value)),children:[(0,W.jsx)(`option`,{value:`10`,children:`10`}),(0,W.jsx)(`option`,{value:`20`,children:`20`}),(0,W.jsx)(`option`,{value:`50`,children:`50`}),(0,W.jsx)(`option`,{value:`100`,children:`100`})]})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:d,children:[d?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>void x(),disabled:!T,children:[(0,W.jsx)(xe,{size:15}),` `,`Previous page`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>void b(),disabled:!E,children:[(0,W.jsx)(Se,{size:15}),` `,`Next page`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{className:`avatar-col`}),(0,W.jsx)(`th`,{children:`Bot ID`}),(0,W.jsx)(`th`,{children:`Username`}),(0,W.jsx)(`th`,{children:`Name`}),(0,W.jsx)(`th`,{children:`Owner`}),(0,W.jsx)(`th`,{children:`Verified`}),(0,W.jsx)(`th`,{children:`Type`}),(0,W.jsx)(`th`,{children:`Created`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[S.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`avatar-col`,children:(0,W.jsx)(`button`,{className:`avatar-link`,type:`button`,onClick:()=>e(`/bots/${t.ID}`),"aria-label":`Open bot ${t.ID}`,children:(0,W.jsx)(En,{id:t.ID,firstName:t.FirstName,username:t.Username})})}),(0,W.jsx)(`td`,{className:`mono`,children:t.ID}),(0,W.jsx)(`td`,{children:H(t.Username)||`-`}),(0,W.jsx)(`td`,{children:t.FirstName||`-`}),(0,W.jsx)(`td`,{className:`mono`,children:t.OwnerUserID>0?t.OwnerUserID:`-`}),(0,W.jsxs)(`td`,{children:[t.Verified?(0,W.jsxs)(J,{tone:`good`,children:[(0,W.jsx)(ee,{size:12}),` `,`Verified`]}):(0,W.jsx)(J,{children:`Not verified`}),` `,(0,W.jsx)(On,{scam:t.Scam,fake:t.Fake})]}),(0,W.jsx)(`td`,{children:t.System?(0,W.jsx)(J,{tone:`warn`,children:`System`}):(0,W.jsx)(J,{children:`User`})}),(0,W.jsx)(`td`,{children:U(t.CreatedAt)}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,onClick:()=>e(`/bots/${t.ID}`),children:[(0,W.jsx)(ge,{size:14}),` `,`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),S.length===0&&(0,W.jsx)(Bt,{colSpan:9})]})]})}),h&&(0,W.jsx)(lr,{onClose:()=>_(!1),onCreated:()=>void y()})]})}function fr({onClose:e,onCreated:t}){let[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(`all`),[o,s]=(0,g.useState)([]),c=(0,g.useMemo)(()=>!n.trim()||i===`selected`&&o.length===0,[n,i,o]);return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Send broadcast`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Broadcasts`}),(0,W.jsx)(`h2`,{children:`Send broadcast`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:e,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsx)(`p`,{children:`Sends a message from the official system account (777000) to all users or to a chosen list. Delivery happens in the background and may take a few minutes for large audiences.`}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsx)(`span`,{children:`Message`}),(0,W.jsx)(`textarea`,{value:n,onChange:e=>r(e.target.value),rows:5,maxLength:4096,placeholder:`What's new...`})]}),(0,W.jsx)(`div`,{className:`bot-create-fields`,children:(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Target`}),(0,W.jsxs)(`select`,{value:i,onChange:e=>a(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All users`}),(0,W.jsx)(`option`,{value:`selected`,children:`Selected users`})]})]})}),i===`selected`&&(0,W.jsx)(Kn,{label:`Recipients`,selected:o,onChange:s})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:e,children:`Close`}),(0,W.jsx)(Z,{label:`Send broadcast`,icon:(0,W.jsx)(et,{size:15}),tone:`neutral`,path:`/api/actions/create-broadcast`,disabled:c,payload:()=>({message:n.trim(),target_mode:i,user_ids:i===`selected`?o.map(e=>e.ID):void 0}),onDone:t})]})]})}),document.body)}var pr={beforeID:0};function mr(){let[e,t]=(0,g.useState)(null),[n,r]=(0,g.useState)([]),[i,a]=(0,g.useState)(pr),[o,s]=(0,g.useState)(!1),[c,l]=(0,g.useState)(``),[u,d]=(0,g.useState)(!1);async function f(e){s(!0),l(``);let n=new URLSearchParams({limit:`50`});e.beforeID&&n.set(`before_id`,String(e.beforeID));try{let e=await k.broadcasts(n);return t(e),e}catch(e){return l(O(e)),null}finally{s(!1)}}async function p(){r([]),a(pr),await f(pr)}async function m(){if(!e?.has_more)return;let t={beforeID:e.next_before_id};await f(t)&&(r(e=>[...e,i]),a(t))}async function h(){if(n.length===0)return;let e=n[n.length-1];await f(e)&&(r(e=>e.slice(0,-1)),a(e))}(0,g.useEffect)(()=>{p()},[]);let _=e?.rows??[],v=_.filter(e=>!e.EnumerationDone||e.SentCount+e.FailedCount0&&!o,b=!!e?.has_more&&!o;return(0,W.jsxs)(G,{title:`Broadcasts`,eyebrow:`Announcements sent from the official system account`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`button`,onClick:()=>d(!0),children:[(0,W.jsx)(et,{size:15}),` `,`Send broadcast`]}),(0,W.jsxs)(`button`,{className:`btn`,type:`button`,onClick:()=>void p(),disabled:o,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]})]}),children:[c&&(0,W.jsx)(q,{children:c}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Campaigns on page`,value:String(_.length)}),(0,W.jsx)(Y,{label:`Still delivering`,value:String(v),tone:v>0?`warn`:`neutral`})]}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Message`}),(0,W.jsx)(`th`,{children:`Target`}),(0,W.jsx)(`th`,{children:`Sent`}),(0,W.jsx)(`th`,{children:`Failed`}),(0,W.jsx)(`th`,{children:`Total`}),(0,W.jsx)(`th`,{children:`Created by`}),(0,W.jsx)(`th`,{children:`Created`})]})}),(0,W.jsxs)(`tbody`,{children:[_.map(e=>{let t=e.SentCount+e.FailedCount,n=e.EnumerationDone&&e.TargetCount>0&&t>=e.TargetCount;return(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:e.ID}),(0,W.jsx)(`td`,{className:`truncate`,children:e.Message}),(0,W.jsx)(`td`,{children:e.TargetMode===`all`?(0,W.jsx)(J,{tone:`warn`,children:`All users`}):(0,W.jsx)(J,{children:`Selected`})}),(0,W.jsx)(`td`,{children:e.SentCount}),(0,W.jsx)(`td`,{children:e.FailedCount>0?(0,W.jsx)(J,{tone:`danger`,children:e.FailedCount}):e.FailedCount}),(0,W.jsx)(`td`,{children:e.TargetCount}),(0,W.jsx)(`td`,{children:e.CreatedBy||`-`}),(0,W.jsxs)(`td`,{children:[U(e.CreatedAt),!n&&(0,W.jsx)(J,{tone:`warn`,children:`Sending`})]})]},e.ID)}),_.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})}),(0,W.jsxs)(`div`,{className:`toolbar`,children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>void h(),disabled:!y,children:[(0,W.jsx)(xe,{size:15}),` `,`Previous page`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>void m(),disabled:!b,children:[o?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(Se,{size:15}),` `,`Next page`]})]}),u&&(0,W.jsx)(fr,{onClose:()=>d(!1),onCreated:()=>void p()})]})}function hr({navigate:e}){let[t,n]=(0,g.useState)(null),[r,i]=(0,g.useState)(``);(0,g.useEffect)(()=>{let e=!1;async function t(){try{let t=await k.dashboard();e||n(t)}catch(t){e||i(t instanceof Error?t.message:`Failed to load dashboard`)}}t();let r=window.setInterval(()=>void t(),15e3);return()=>{e=!0,window.clearInterval(r)}},[]);let a=t?.counts,o=t?.storage,s=t?.host;return(0,W.jsxs)(`div`,{className:`dashboard-layout`,children:[r&&(0,W.jsx)(q,{children:r}),(0,W.jsxs)(gr,{title:`Needs attention`,children:[(0,W.jsx)(_r,{icon:(0,W.jsx)(je,{}),label:`Pending reports`,value:a?Dt(String(a.PendingReports)):`…`,tone:a&&a.PendingReports>0?`warn`:`good`,href:`/moderation`,navigate:e}),(0,W.jsx)(_r,{icon:(0,W.jsx)(ee,{}),label:`Verification requests`,value:a?Dt(String(a.PendingVerifications)):`…`,tone:a&&a.PendingVerifications>0?`warn`:`good`,href:`/verification`,navigate:e})]}),(0,W.jsxs)(gr,{title:`People & chats`,children:[(0,W.jsx)(_r,{icon:(0,W.jsx)(_t,{}),label:`Users`,value:a?Dt(String(a.Users)):`…`,href:`/accounts`,navigate:e}),(0,W.jsx)(_r,{icon:(0,W.jsx)(le,{}),label:`Online now`,value:a?Dt(String(a.OnlineUsers)):`…`,sub:`last 5 min`,href:`/accounts`,navigate:e}),(0,W.jsx)(_r,{icon:(0,W.jsx)(ge,{}),label:`Bots`,value:a?Dt(String(a.Bots)):`…`,href:`/bots`,navigate:e}),(0,W.jsx)(_r,{icon:(0,W.jsx)(Qe,{}),label:`Channels`,value:a?Dt(String(a.BroadcastChannels)):`…`,href:`/channels`,navigate:e}),(0,W.jsx)(_r,{icon:(0,W.jsx)(ce,{}),label:`Supergroups`,value:a?Dt(String(a.Supergroups)):`…`,href:`/channels`,navigate:e})]}),(0,W.jsxs)(gr,{title:`Content`,children:[(0,W.jsx)(_r,{icon:(0,W.jsx)(dt,{}),label:`Sticker packs`,value:a?Dt(String(a.StickerSets)):`…`,href:`/stickers`,navigate:e}),(0,W.jsx)(_r,{icon:(0,W.jsx)(lt,{}),label:`Emoji packs`,value:a?Dt(String(a.EmojiSets)):`…`,href:`/emoji`,navigate:e}),(0,W.jsx)(_r,{icon:(0,W.jsx)(Ae,{}),label:`GIFs`,value:a?Dt(String(a.Gifs)):`…`,sub:`saved by users`,href:`/gif-catalog`,navigate:e}),(0,W.jsx)(_r,{icon:(0,W.jsx)(Te,{}),label:`Media storage used`,value:o?Pt(o.PhysicalBytes):`…`,sub:o?`${o.BackendKind} backend`:void 0,href:`/storage`,navigate:e})]}),(0,W.jsxs)(gr,{title:`Server health`,hint:s?.Ready?void 0:`waiting for first sample…`,children:[(0,W.jsx)(vr,{icon:(0,W.jsx)(we,{}),label:`CPU load`,percent:s?.Ready?s.CPUPercent:void 0,valueText:s?.Ready?`${s.CPUPercent.toFixed(0)}%`:`…`}),(0,W.jsx)(vr,{icon:(0,W.jsx)(Ue,{}),label:`RAM used`,percent:s?.Ready&&s.MemTotalBytes>0?s.MemUsedBytes/s.MemTotalBytes*100:void 0,valueText:s?.Ready?Pt(String(s.MemUsedBytes)):`…`,sub:s?.Ready?`of ${Pt(String(s.MemTotalBytes))}`:void 0}),(0,W.jsx)(vr,{icon:(0,W.jsx)(Pe,{}),label:`Disk free`,percent:s?.Ready&&s.DiskTotalBytes>0?(s.DiskTotalBytes-s.DiskFreeBytes)/s.DiskTotalBytes*100:void 0,valueText:s?.Ready?Pt(String(s.DiskFreeBytes)):`…`,sub:s?.Ready?`of ${Pt(String(s.DiskTotalBytes))}`:void 0,warnAbove:85})]})]})}function gr({title:e,hint:t,children:n}){return(0,W.jsxs)(`div`,{className:`dashboard-section`,children:[(0,W.jsxs)(`div`,{className:`dashboard-section-title`,children:[e,t&&(0,W.jsx)(`span`,{children:t})]}),(0,W.jsx)(`div`,{className:`dashboard-grid`,children:n})]})}function _r({icon:e,label:t,value:n,sub:r,tone:i=`neutral`,href:a,navigate:o}){let s=i===`neutral`?``:` ${i}`,c=(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`div`,{className:`stat-tile-head`,children:[(0,W.jsx)(`span`,{className:`stat-tile-icon`,children:e}),i===`warn`&&(0,W.jsx)(oe,{size:15,className:`stat-tile-open`})]}),(0,W.jsx)(`div`,{className:`stat-tile-value`,children:n}),(0,W.jsx)(`div`,{className:`stat-tile-label`,children:t}),r&&(0,W.jsx)(`div`,{className:`stat-tile-sub`,children:r})]});return a&&o?(0,W.jsx)(`a`,{className:`stat-tile clickable${s}`,href:a,onClick:e=>{e.preventDefault(),o(a)},children:c}):(0,W.jsx)(`div`,{className:`stat-tile${s}`,children:c})}function vr({icon:e,label:t,percent:n,valueText:r,sub:i,warnAbove:a=90}){let o=n===void 0?0:Math.max(0,Math.min(100,n)),s=n===void 0?`neutral`:n>=a?`danger`:n>=a-15?`warn`:`neutral`;return(0,W.jsxs)(`div`,{className:`stat-tile${s===`neutral`?``:` ${s}`}`,children:[(0,W.jsx)(`div`,{className:`stat-tile-head`,children:(0,W.jsx)(`span`,{className:`stat-tile-icon`,children:e})}),(0,W.jsx)(`div`,{className:`stat-tile-value`,children:r}),(0,W.jsx)(`div`,{className:`stat-tile-label`,children:t}),i&&(0,W.jsx)(`div`,{className:`stat-tile-sub`,children:i}),(0,W.jsx)(`div`,{className:`stat-tile-bar`,children:(0,W.jsx)(`span`,{style:{width:`${o}%`}})})]})}function Q({channelID:e,msgID:t,navigate:n}){let[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``);async function s(){o(``);try{i(await k.groupMessage(e,t))}catch(e){o(O(e))}}if((0,g.useEffect)(()=>{s()},[e,t]),a)return(0,W.jsx)(q,{children:a});if(!r)return(0,W.jsx)(Vt,{label:`Loading`});let c=r.Message;return(0,W.jsx)(G,{title:`Group Message #${c.ID}`,eyebrow:`Message Detail`,actions:(0,W.jsxs)(`button`,{className:`btn icon-text`,onClick:()=>n(`/messages/groups`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to group messages`]}),children:(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:`Channel / Group ${c.ChannelID}`}),(0,W.jsx)(`div`,{className:`entity-subtitle`,children:`Sender ${c.SenderUserID} · ${wt(c.Date)}`})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[c.Deleted?(0,W.jsx)(J,{tone:`danger`,children:`Deleted`}):(0,W.jsx)(J,{children:`Live`}),c.Pinned&&(0,W.jsx)(J,{tone:`warn`,children:`Pinned`}),c.Post&&(0,W.jsx)(J,{children:`Channel post`}),(0,W.jsxs)(J,{children:[`pts `,c.PTS]})]})]}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Message ID`,value:String(c.ID),mono:!0}),(0,W.jsx)(X,{label:`Channel / Group`,value:String(c.ChannelID),mono:!0}),(0,W.jsx)(X,{label:`From Peer`,value:`${c.FromPeerType}:${c.FromPeerID}`,mono:!0}),(0,W.jsx)(X,{label:`Views`,value:String(c.ViewsCount)})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Channel Message Row`,text:`channel_messages read-only snapshot`}),(0,W.jsx)(Ht,{value:r.MessageJSON})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Channel Row`,text:`channels read-only snapshot`}),(0,W.jsx)(Ht,{value:r.ChannelJSON})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Channel Update Events`,text:`durable channel_update_events`}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`PTS`}),(0,W.jsx)(`th`,{children:`Count`}),(0,W.jsx)(`th`,{children:`Type`}),(0,W.jsx)(`th`,{children:`Message ID`}),(0,W.jsx)(`th`,{children:`Sender`}),(0,W.jsx)(`th`,{children:`Time`})]})}),(0,W.jsxs)(`tbody`,{children:[r.UpdateEvents.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{children:e.PTS}),(0,W.jsx)(`td`,{children:e.PTSCount}),(0,W.jsx)(`td`,{children:e.Type}),(0,W.jsx)(`td`,{children:e.MessageID}),(0,W.jsx)(`td`,{children:e.SenderUserID}),(0,W.jsx)(`td`,{children:wt(e.Date)})]},`${e.PTS}-${e.Type}-${e.MessageID}`)),r.UpdateEvents.length===0&&(0,W.jsx)(Bt,{colSpan:6})]})]})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Event JSON`}),(0,W.jsxs)(`div`,{className:`raw-grid`,children:[r.UpdateEvents.map(e=>(0,W.jsx)(Ht,{value:e.JSON},`${e.PTS}-${e.Type}-json`)),r.UpdateEvents.length===0&&(0,W.jsx)(`div`,{className:`empty-panel`,children:`No results`})]})]})]})})}function yr({navigate:e}){let[t,n]=(0,g.useState)(null),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(`100`),[l,u]=(0,g.useState)(null),[d,f]=(0,g.useState)(``);async function p(e=!1){if(f(``),!t){f(`Search and select a supergroup or channel first`);return}let n=new URLSearchParams({channel_id:String(t.ID),limit:s});if(e&&l?.rows.length){let e=l.rows[l.rows.length-1];n.set(`before_date`,String(e.Date)),n.set(`before_id`,String(e.ID)),i(String(e.Date)),o(String(e.ID))}else r&&n.set(`before_date`,r),a&&n.set(`before_id`,a);try{u(await k.groupMessages(n))}catch(e){f(O(e))}}function m(e){n(e),i(``),o(``),u(null)}let h=l?.rows??[];return(0,W.jsxs)(G,{title:`Group Messages`,eyebrow:`Supergroup / channel messages`,children:[d&&(0,W.jsx)(q,{children:d}),(0,W.jsxs)(Lt,{children:[(0,W.jsx)(`div`,{className:`message-selector-grid single`,children:(0,W.jsx)(Jn,{label:`Channel / Group`,value:t,onChange:m})}),(0,W.jsxs)(`form`,{className:`toolbar message-query`,onSubmit:e=>{e.preventDefault(),p(!1)},children:[(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`before_date cursor`}),(0,W.jsx)(`input`,{value:a,onChange:e=>o(e.target.value),placeholder:`before_msg_id cursor`}),(0,W.jsx)(`input`,{className:`small-input`,value:s,onChange:e=>c(e.target.value),placeholder:`limit <= 100`}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,children:[(0,W.jsx)(V,{size:15}),` `,`Search messages`]}),h.length?(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>p(!0),children:[(0,W.jsx)(Se,{size:15}),` `,`Next page`]}):null]})]}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Messages on page`,value:String(h.length)}),(0,W.jsx)(Y,{label:`With media`,value:String(h.filter(e=>e.Media&&e.Media!==`{}`).length)}),(0,W.jsx)(Y,{label:`Channel posts`,value:String(h.filter(e=>e.Post).length)}),(0,W.jsx)(Y,{label:`Channel / Group`,value:t?`${t.Title||Ct(t)} (${t.ID})`:`-`})]}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Message ID`}),(0,W.jsx)(`th`,{children:`Time`}),(0,W.jsx)(`th`,{children:`Sender`}),(0,W.jsx)(`th`,{children:`From Peer`}),(0,W.jsx)(`th`,{children:`PTS`}),(0,W.jsx)(`th`,{children:`Views`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Body`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[h.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:t.ID}),(0,W.jsx)(`td`,{children:wt(t.Date)}),(0,W.jsx)(`td`,{className:`mono`,children:t.SenderUserID}),(0,W.jsxs)(`td`,{className:`mono`,children:[t.FromPeerType,`:`,t.FromPeerID]}),(0,W.jsx)(`td`,{children:t.PTS}),(0,W.jsx)(`td`,{children:t.ViewsCount}),(0,W.jsx)(`td`,{children:t.Deleted?(0,W.jsx)(J,{tone:`danger`,children:`Deleted`}):t.Pinned?(0,W.jsx)(J,{tone:`warn`,children:`Pinned`}):(0,W.jsx)(J,{children:`Live`})}),(0,W.jsx)(`td`,{className:`truncate`,children:t.Body}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,onClick:()=>e(`/messages/groups/detail?channel_id=${t.ChannelID}&msg_id=${t.ID}`),children:[`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},`${t.ChannelID}-${t.ID}`)),h.length===0&&(0,W.jsx)(Bt,{colSpan:9})]})]})})]})}function br({ownerUserID:e,msgID:t,navigate:n}){let[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``);async function s(){o(``);try{i(await k.message(e,t))}catch(e){o(O(e))}}if((0,g.useEffect)(()=>{s()},[e,t]),a)return(0,W.jsx)(q,{children:a});if(!r)return(0,W.jsx)(Vt,{label:`Loading`});let c=r.Message;return(0,W.jsx)(G,{title:`Message #${c.BoxID}`,eyebrow:`Message Detail`,actions:(0,W.jsxs)(`button`,{className:`btn icon-text`,onClick:()=>n(`/messages/private`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to private messages`]}),children:(0,W.jsx)(Rt,{main:(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:`Owner ${c.OwnerUserID} · Peer ${c.PeerID}`}),(0,W.jsx)(`div`,{className:`entity-subtitle`,children:`Sender ${c.FromUserID} · ${wt(c.Date)}`})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[c.Deleted?(0,W.jsx)(J,{tone:`danger`,children:`Deleted`}):(0,W.jsx)(J,{children:`Live`}),(0,W.jsxs)(J,{children:[`pts `,c.PTS]}),(0,W.jsx)(J,{children:c.Outgoing?`Outgoing`:`Incoming`})]})]}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Message box ID`,value:String(c.BoxID),mono:!0}),(0,W.jsx)(X,{label:`Private message ID`,value:String(c.PrivateMessageID),mono:!0}),(0,W.jsx)(X,{label:`Message sender`,value:String(c.MessageSenderID),mono:!0}),(0,W.jsx)(X,{label:`Time`,value:wt(c.Date)})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Message Box`,text:`message_boxes read-only snapshot`}),(0,W.jsx)(Ht,{value:r.MessageJSON})]}),(0,W.jsxs)(`div`,{className:`raw-grid`,children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Dialog Row`,text:`dialogs read-only snapshot`}),(0,W.jsx)(Ht,{value:r.DialogJSON})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Private Message Row`,text:`private_messages read-only snapshot`}),(0,W.jsx)(Ht,{value:r.PrivateJSON})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Update Events`,text:`durable user_update_events`}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`PTS`}),(0,W.jsx)(`th`,{children:`Count`}),(0,W.jsx)(`th`,{children:`Type`}),(0,W.jsx)(`th`,{children:`Time`})]})}),(0,W.jsxs)(`tbody`,{children:[r.UpdateEvents.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{children:e.PTS}),(0,W.jsx)(`td`,{children:e.PTSCount}),(0,W.jsx)(`td`,{children:e.Type}),(0,W.jsx)(`td`,{children:wt(e.Date)})]},`${e.PTS}-${e.Type}`)),r.UpdateEvents.length===0&&(0,W.jsx)(Bt,{colSpan:4})]})]})})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Dispatch Queue`,text:`online/offline dispatch_outbox`}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`User ID`}),(0,W.jsx)(`th`,{children:`PTS`}),(0,W.jsx)(`th`,{children:`Type`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Attempts`}),(0,W.jsx)(`th`,{children:`Updated`})]})}),(0,W.jsxs)(`tbody`,{children:[r.Outbox.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{children:e.ID}),(0,W.jsx)(`td`,{children:e.TargetUserID}),(0,W.jsx)(`td`,{children:e.PTS}),(0,W.jsx)(`td`,{children:e.EventType}),(0,W.jsx)(`td`,{children:e.Status}),(0,W.jsx)(`td`,{children:e.Attempts}),(0,W.jsx)(`td`,{children:U(e.UpdatedAt)})]},e.ID)),r.Outbox.length===0&&(0,W.jsx)(Bt,{colSpan:7})]})]})})]})]}),side:(0,W.jsxs)(`section`,{className:`action-dock`,children:[(0,W.jsx)(`div`,{className:`dock-title`,children:`Operations`}),(0,W.jsx)(Z,{label:`Delete this message`,icon:(0,W.jsx)(pt,{size:15}),path:`/api/actions/delete-messages`,payload:()=>({owner_user_id:c.OwnerUserID,peer_id:c.PeerID,ids:[c.BoxID],revoke:!0}),onDone:s})]})})})}function xr({navigate:e}){let[t,n]=(0,g.useState)(null),[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(`100`),[d,f]=(0,g.useState)(``),[p,m]=(0,g.useState)(!0),[h,_]=(0,g.useState)(!1),[v,y]=(0,g.useState)(``),[b,x]=(0,g.useState)(`1`),[S,C]=(0,g.useState)(null),[w,T]=(0,g.useState)(``);async function E(e=!1){if(T(``),!t||!r){T(`Search and select the owner user and peer user first`);return}let n=new URLSearchParams({owner_user_id:String(t.ID),peer_id:String(r.ID),limit:l});if(e&&S?.rows.length){let e=S.rows[S.rows.length-1];n.set(`before_date`,String(e.Date)),n.set(`before_id`,String(e.BoxID)),o(String(e.Date)),c(String(e.BoxID))}else a&&n.set(`before_date`,a),s&&n.set(`before_id`,s);try{C(await k.messages(n))}catch(e){T(O(e))}}function D(e){n(e),o(``),c(``),C(null)}function A(e){i(e),o(``),c(``),C(null)}return(0,W.jsxs)(G,{title:`Private Messages`,eyebrow:`Private message boxes`,children:[w&&(0,W.jsx)(q,{children:w}),(0,W.jsxs)(Lt,{children:[(0,W.jsxs)(`div`,{className:`message-selector-grid`,children:[(0,W.jsx)(Gn,{label:`Owner user`,value:t,onChange:D}),(0,W.jsx)(Gn,{label:`Peer user`,value:r,onChange:A})]}),(0,W.jsxs)(`form`,{className:`toolbar message-query`,onSubmit:e=>{e.preventDefault(),E(!1)},children:[(0,W.jsx)(`input`,{value:a,onChange:e=>o(e.target.value),placeholder:`before_date cursor`}),(0,W.jsx)(`input`,{value:s,onChange:e=>c(e.target.value),placeholder:`before_msg_id cursor`}),(0,W.jsx)(`input`,{className:`small-input`,value:l,onChange:e=>u(e.target.value),placeholder:`limit <= 100`}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,children:[(0,W.jsx)(V,{size:15}),` `,`Search messages`]}),S?.rows.length?(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>E(!0),children:[(0,W.jsx)(Se,{size:15}),` `,`Next page`]}):null]})]}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Messages on page`,value:String(S?.rows.length??0)}),(0,W.jsx)(Y,{label:`Deleted`,value:String((S?.rows??[]).filter(e=>e.Deleted).length),tone:`danger`}),(0,W.jsx)(Y,{label:`Outgoing`,value:String((S?.rows??[]).filter(e=>e.Outgoing).length)}),(0,W.jsx)(Y,{label:`Owner / Peer`,value:t&&r?`${St(t)} / ${St(r)}`:`-`})]}),(0,W.jsxs)(`div`,{className:`operation-row`,children:[(0,W.jsxs)(`div`,{className:`operation-box`,children:[(0,W.jsxs)(`div`,{className:`operation-title`,children:[(0,W.jsx)(pt,{size:15}),` `,`Delete selected messages`]}),(0,W.jsx)(`input`,{value:d,onChange:e=>f(e.target.value),placeholder:`Message IDs, comma separated`}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:p,onChange:e=>m(e.target.checked)}),` `,`Revoke for both sides`]}),(0,W.jsx)(Z,{path:`/api/actions/delete-messages`,label:`Dry-run delete`,payload:()=>({owner_user_id:t?.ID??0,peer_id:r?.ID??0,ids:Ft(d,`Message IDs are invalid`),revoke:p})})]}),(0,W.jsxs)(`div`,{className:`operation-box`,children:[(0,W.jsxs)(`div`,{className:`operation-title`,children:[(0,W.jsx)(Fe,{size:15}),` `,`Clear private history`]}),(0,W.jsx)(`input`,{value:v,onChange:e=>y(e.target.value),placeholder:`max_id cutoff`}),(0,W.jsx)(`input`,{value:b,onChange:e=>x(e.target.value),placeholder:`max_batches`}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:p,onChange:e=>m(e.target.checked)}),` `,`Revoke for both sides`]}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:h,onChange:e=>_(e.target.checked)}),` `,`Clear only this side`]}),(0,W.jsx)(Z,{path:`/api/actions/delete-history`,label:`Dry-run clear history`,payload:()=>({owner_user_id:t?.ID??0,peer_id:r?.ID??0,max_id:Et(v),max_batches:Et(b),just_clear:h,revoke:p})})]})]}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Message ID`}),(0,W.jsx)(`th`,{children:`Time`}),(0,W.jsx)(`th`,{children:`Sender`}),(0,W.jsx)(`th`,{children:`Direction`}),(0,W.jsx)(`th`,{children:`PTS`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Body`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[S?.rows.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:t.BoxID}),(0,W.jsx)(`td`,{children:wt(t.Date)}),(0,W.jsx)(`td`,{className:`mono`,children:t.FromUserID}),(0,W.jsx)(`td`,{children:t.Outgoing?`Outgoing`:`Incoming`}),(0,W.jsx)(`td`,{children:t.PTS}),(0,W.jsx)(`td`,{children:t.Deleted?(0,W.jsx)(J,{tone:`danger`,children:`Deleted`}):(0,W.jsx)(J,{children:`Live`})}),(0,W.jsx)(`td`,{className:`truncate`,children:t.Body}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,onClick:()=>e(`/messages/private/detail?owner_user_id=${t.OwnerUserID}&msg_id=${t.BoxID}`),children:[`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},`${t.OwnerUserID}-${t.BoxID}`)),(!S||S.rows.length===0)&&(0,W.jsx)(Bt,{colSpan:8})]})]})})]})}var Sr=c(o(((e,t)=>{typeof document<`u`&&typeof navigator<`u`&&(function(n,r){typeof e==`object`&&t!==void 0?t.exports=r():typeof define==`function`&&define.amd?define(r):(n=typeof globalThis<`u`?globalThis:n||self,n.lottie=r())})(e,(function(){var n=``,r=!1,i=-999999,a=function(e){r=!!e},o=function(){return r},s=function(e){n=e},c=function(){return n};function l(e){return document.createElement(e)}function u(e,t){var n,r=e.length,i;for(n=0;n1?n[1]=1:n[1]<=0&&(n[1]=0),F(n[0],n[1],n[2])}function I(e,t){var n=ne(e[0]*255,e[1]*255,e[2]*255);return n[2]+=t,n[2]>1?n[2]=1:n[2]<0&&(n[2]=0),F(n[0],n[1],n[2])}function ie(e,t){var n=ne(e[0]*255,e[1]*255,e[2]*255);return n[0]+=t/360,n[0]>1?--n[0]:n[0]<0&&(n[0]+=1),F(n[0],n[1],n[2])}(function(){var e=[],t,n;for(t=0;t<256;t+=1)n=t.toString(16),e[t]=n.length===1?`0`+n:n;return function(t,n,r){return t<0&&(t=0),n<0&&(n=0),r<0&&(r=0),`#`+e[t]+e[n]+e[r]}})();var ae=function(e){g=!!e},oe=function(){return g},se=function(e){_=e},ce=function(){return _},le=function(){return v},ue=function(e){E=e},de=function(){return E},fe=function(e){y=e};function L(e){return document.createElementNS(`http://www.w3.org/2000/svg`,e)}function pe(e){"@babel/helpers - typeof";return pe=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},pe(e)}var me=function(){var e=1,t=[],n,r,i={onmessage:function(){},postMessage:function(e){n({data:e})}},a={postMessage:function(e){i.onmessage({data:e})}};function s(e){if(window.Worker&&window.Blob&&o()){var t=new Blob([`var _workerSelf = self; self.onmessage = `,e.toString()],{type:`text/javascript`}),r=URL.createObjectURL(t);return new Worker(r)}return n=e,i}function c(){r||(r=s(function(e){function t(){function e(t,n){var o,s,c=t.length,l,u,d,f;for(s=0;s=0;--t)if(e[t].ty===`sh`)if(e[t].ks.k.i)a(e[t].ks.k);else for(o=e[t].ks.k.length,r=0;rn[0]?!0:n[0]>e[0]?!1:e[1]>n[1]?!0:n[1]>e[1]?!1:e[2]>n[2]?!0:n[2]>e[2]?!1:null}var s=function(){var e=[4,4,14];function t(e){var t=e.t.d;e.t.d={k:[{s:t,t:0}]}}function n(e){var n,r=e.length;for(n=0;n=0;--n)if(e[n].ty===`sh`)if(e[n].ks.k.i)e[n].ks.k.c=e[n].closed;else for(a=e[n].ks.k.length,i=0;i500)&&(this._imageLoaded(),clearInterval(n)),t+=1}.bind(this),50)}function a(t){var n=r(t,this.assetsPath,this.path),i=L(`image`);b?this.testImageLoaded(i):i.addEventListener(`load`,this._imageLoaded,!1),i.addEventListener(`error`,function(){a.img=e,this._imageLoaded()}.bind(this),!1),i.setAttributeNS(`http://www.w3.org/1999/xlink`,`href`,n),this._elementHelper.append?this._elementHelper.append(i):this._elementHelper.appendChild(i);var a={img:i,assetData:t};return a}function o(t){var n=r(t,this.assetsPath,this.path),i=l(`img`);i.crossOrigin=`anonymous`,i.addEventListener(`load`,this._imageLoaded,!1),i.addEventListener(`error`,function(){a.img=e,this._imageLoaded()}.bind(this),!1),i.src=n;var a={img:i,assetData:t};return a}function s(e){var t={assetData:e},n=r(e,this.assetsPath,this.path);return me.loadData(n,function(e){t.img=e,this._footageLoaded()}.bind(this),function(){t.img={},this._footageLoaded()}.bind(this)),t}function c(e,t){this.imagesLoadedCb=t;var n,r=e.length;for(n=0;nthis.animationData.op&&(this.animationData.op=e.op,this.totalFrames=Math.floor(e.op-this.animationData.ip));var t=this.animationData.layers,n,r=t.length,i=e.layers,a,o=i.length;for(a=0;athis.timeCompleted&&(this.currentFrame=this.timeCompleted),this.trigger(`enterFrame`),this.renderFrame(),this.trigger(`drawnFrame`)},R.prototype.renderFrame=function(){if(!(this.isLoaded===!1||!this.renderer))try{this.expressionsPlugin&&this.expressionsPlugin.resetFrame(),this.renderer.renderFrame(this.currentFrame+this.firstFrame)}catch(e){this.triggerRenderFrameError(e)}},R.prototype.play=function(e){e&&this.name!==e||this.isPaused===!0&&(this.isPaused=!1,this.trigger(`_play`),this.audioController.resume(),this._idle&&(this._idle=!1,this.trigger(`_active`)))},R.prototype.pause=function(e){e&&this.name!==e||this.isPaused===!1&&(this.isPaused=!0,this.trigger(`_pause`),this._idle=!0,this.trigger(`_idle`),this.audioController.pause())},R.prototype.togglePause=function(e){e&&this.name!==e||(this.isPaused===!0?this.play():this.pause())},R.prototype.stop=function(e){e&&this.name!==e||(this.pause(),this.playCount=0,this._completedLoop=!1,this.setCurrentRawFrameValue(0))},R.prototype.getMarkerData=function(e){for(var t,n=0;n=this.totalFrames-1&&this.frameModifier>0?!this.loop||this.playCount===this.loop?this.checkSegments(t>this.totalFrames?t%this.totalFrames:0)||(n=!0,t=this.totalFrames-1):t>=this.totalFrames?(this.playCount+=1,this.checkSegments(t%this.totalFrames)||(this.setCurrentRawFrameValue(t%this.totalFrames),this._completedLoop=!0,this.trigger(`loopComplete`))):this.setCurrentRawFrameValue(t):t<0?this.checkSegments(t%this.totalFrames)||(this.loop&&!(this.playCount--<=0&&this.loop!==!0)?(this.setCurrentRawFrameValue(this.totalFrames+t%this.totalFrames),this._completedLoop?this.trigger(`loopComplete`):this._completedLoop=!0):(n=!0,t=0)):this.setCurrentRawFrameValue(t),n&&(this.setCurrentRawFrameValue(t),this.pause(),this.trigger(`complete`))}},R.prototype.adjustSegment=function(e,t){this.playCount=0,e[1]0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(-1)),this.totalFrames=e[0]-e[1],this.timeCompleted=this.totalFrames,this.firstFrame=e[1],this.setCurrentRawFrameValue(this.totalFrames-.001-t)):e[1]>e[0]&&(this.frameModifier<0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(1)),this.totalFrames=e[1]-e[0],this.timeCompleted=this.totalFrames,this.firstFrame=e[0],this.setCurrentRawFrameValue(.001+t)),this.trigger(`segmentStart`)},R.prototype.setSegment=function(e,t){var n=-1;this.isPaused&&(this.currentRawFrame+this.firstFramet&&(n=t-e)),this.firstFrame=e,this.totalFrames=t-e,this.timeCompleted=this.totalFrames,n!==-1&&this.goToAndStop(n,!0)},R.prototype.playSegments=function(e,t){if(t&&(this.segments.length=0),Ce(e[0])===`object`){var n,r=e.length;for(n=0;n=0;--n)t[n].animation.destroy(e)}function T(e,t,n){var r=[].concat([].slice.call(document.getElementsByClassName(`lottie`)),[].slice.call(document.getElementsByClassName(`bodymovin`))),i,a=r.length;for(i=0;i0?n=c:t=c;while(Math.abs(s)>a&&++l=i?g(e,d,t,n):f===0?d:h(e,a,a+c,t,n)}},e}(),Ee=function(){function e(e){return e.concat(m(e.length))}return{double:e}}(),De=function(){return function(e,t,n){var r=0,i=e,a=m(i),o={newElement:s,release:c};function s(){var e;return r?(--r,e=a[r]):e=t(),e}function c(e){r===i&&(a=Ee.double(a),i*=2),n&&n(e),a[r]=e,r+=1}return o}}(),Oe=function(){function e(){return{addedLength:0,percents:p(`float32`,de()),lengths:p(`float32`,de())}}return De(8,e)}(),ke=function(){function e(){return{lengths:[],totalLength:0}}function t(e){var t,n=e.lengths.length;for(t=0;t-.001&&o<.001}function n(n,r,i,a,o,s,c,l,u){if(i===0&&s===0&&u===0)return t(n,r,a,o,c,l);var d=e.sqrt(e.pow(a-n,2)+e.pow(o-r,2)+e.pow(s-i,2)),f=e.sqrt(e.pow(c-n,2)+e.pow(l-r,2)+e.pow(u-i,2)),p=e.sqrt(e.pow(c-a,2)+e.pow(l-o,2)+e.pow(u-s,2)),m=d>f?d>p?d-f-p:p-f-d:p>f?p-f-d:f-d-p;return m>-1e-4&&m<1e-4}var r=function(){return function(e,t,n,r){var i=de(),a,o,s,c,l,u=0,d,f=[],p=[],m=Oe.newElement();for(s=n.length,a=0;ao?-1:1,l=!0;l;)if(r[a]<=o&&r[a+1]>o?(s=(o-r[a])/(r[a+1]-r[a]),l=!1):a+=c,a<0||a>=i-1){if(a===i-1)return n[a];l=!1}return n[a]+(n[a+1]-n[a])*s}function l(t,n,r,i,a,o){var s=c(a,o),l=1-s;return[e.round((l*l*l*t[0]+(s*l*l+l*s*l+l*l*s)*r[0]+(s*s*l+l*s*s+s*l*s)*i[0]+s*s*s*n[0])*1e3)/1e3,e.round((l*l*l*t[1]+(s*l*l+l*s*l+l*l*s)*r[1]+(s*s*l+l*s*s+s*l*s)*i[1]+s*s*s*n[1])*1e3)/1e3]}var u=p(`float32`,8);function d(t,n,r,i,a,o,s){a<0?a=0:a>1&&(a=1);var l=c(a,s);o=o>1?1:o;var d=c(o,s),f,p=t.length,m=1-l,h=1-d,g=m*m*m,_=l*m*m*3,v=l*l*m*3,y=l*l*l,b=m*m*h,x=l*m*h+m*l*h+m*m*d,S=l*l*h+m*l*d+l*m*d,C=l*l*d,w=m*h*h,T=l*h*h+m*d*h+m*h*d,E=l*d*h+m*d*d+l*h*d,D=l*d*d,O=h*h*h,k=d*h*h+h*d*h+h*h*d,A=d*d*h+h*d*d+d*h*d,j=d*d*d;for(f=0;f=l.t-n){c.h&&(c=l),i=0;break}if(l.t-n>e){i=a;break}a=v||e=v?x.points.length-1:0;for(f=x.points[S].point.length,d=0;d=T&&C=v)r[0]=b[0],r[1]=b[1],r[2]=b[2];else if(e<=y)r[0]=c.s[0],r[1]=c.s[1],r[2]=c.s[2];else{var j=Le(c.s),M=Le(b),N=(e-y)/(v-y);Ie(r,Fe(j,M,N))}else for(a=0;a=v?m=1:e1e-6?(f=Math.acos(p),m=Math.sin(f),h=Math.sin((1-n)*f)/m,g=Math.sin(n*f)/m):(h=1-n,g=n),r[0]=h*i+g*c,r[1]=h*a+g*l,r[2]=h*o+g*u,r[3]=h*s+g*d,r}function Ie(e,t){var n=t[0],r=t[1],i=t[2],a=t[3],o=Math.atan2(2*r*a-2*n*i,1-2*r*r-2*i*i),s=Math.asin(2*n*r+2*i*a),c=Math.atan2(2*n*a-2*r*i,1-2*n*n-2*i*i);e[0]=o/D,e[1]=s/D,e[2]=c/D}function Le(e){var t=e[0]*D,n=e[1]*D,r=e[2]*D,i=Math.cos(t/2),a=Math.cos(n/2),o=Math.cos(r/2),s=Math.sin(t/2),c=Math.sin(n/2),l=Math.sin(r/2),u=i*a*o-s*c*l;return[s*c*o+i*a*l,s*a*o+i*c*l,i*c*o-s*a*l,u]}function Re(){var e=this.comp.renderedFrame-this.offsetTime,t=this.keyframes[0].t-this.offsetTime,n=this.keyframes[this.keyframes.length-1].t-this.offsetTime;if(!(e===this._caching.lastFrame||this._caching.lastFrame!==Me&&(this._caching.lastFrame>=n&&e>=n||this._caching.lastFrame=e&&(this._caching._lastKeyframeIndex=-1,this._caching.lastIndex=0);var r=this.interpolateValue(e,this._caching);this.pv=r}return this._caching.lastFrame=e,this.pv}function ze(e){var t;if(this.propType===`unidimensional`)t=e*this.mult,Ne(this.v-t)>1e-5&&(this.v=t,this._mdf=!0);else for(var n=0,r=this.v.length;n1e-5&&(this.v[n]=t,this._mdf=!0),n+=1}function Be(){if(!(this.elem.globalData.frameId===this.frameId||!this.effectsSequence.length)){if(this.lock){this.setVValue(this.pv);return}this.lock=!0,this._mdf=this._isFirstFrame;var e,t=this.effectsSequence.length,n=this.kf?this.pv:this.data.k;for(e=0;e=this._maxLength&&this.doubleArrayLength(),n){case`v`:a=this.v;break;case`i`:a=this.i;break;case`o`:a=this.o;break;default:a=[];break}(!a[r]||a[r]&&!i)&&(a[r]=qe.newElement()),a[r][0]=e,a[r][1]=t},Je.prototype.setTripleAt=function(e,t,n,r,i,a,o,s){this.setXYAt(e,t,`v`,o,s),this.setXYAt(n,r,`o`,o,s),this.setXYAt(i,a,`i`,o,s)},Je.prototype.reverse=function(){var e=new Je;e.setPathData(this.c,this._length);var t=this.v,n=this.o,r=this.i,i=0;this.c&&(e.setTripleAt(t[0][0],t[0][1],r[0][0],r[0][1],n[0][0],n[0][1],0,!1),i=1);var a=this._length-1,o=this._length,s;for(s=i;s=p[p.length-1].t-this.offsetTime)i=p[p.length-1].s?p[p.length-1].s[0]:p[p.length-2].e[0],o=!0;else{for(var m=r,h=p.length-1,g=!0,_,v,y;g&&(_=p[m],v=p[m+1],!(v.t-this.offsetTime>e));)m=v.t-this.offsetTime)d=1;else if(e<_.t-this.offsetTime)d=0;else{var b;y.__fnct?b=y.__fnct:(b=Te.getBezierEasing(_.o.x,_.o.y,_.i.x,_.i.y).get,y.__fnct=b),d=b((e-(_.t-this.offsetTime))/(v.t-this.offsetTime-(_.t-this.offsetTime)))}a=v.s?v.s[0]:_.e[0]}i=_.s[0]}for(l=t._length,u=i.i[0].length,n.lastIndex=r,s=0;sr&&t>r)||(this._caching.lastIndex=i0||e>-1e-6&&e<0?r(e*t)/t:e}function P(){var e=this.props,t=N(e[0]),n=N(e[1]),r=N(e[4]),i=N(e[5]),a=N(e[12]),o=N(e[13]);return`matrix(`+t+`,`+n+`,`+r+`,`+i+`,`+a+`,`+o+`)`}return function(){this.reset=i,this.rotate=a,this.rotateX=o,this.rotateY=s,this.rotateZ=c,this.skew=u,this.skewFromAxis=d,this.shear=l,this.scale=f,this.setTransform=m,this.translate=h,this.transform=g,this.multiply=_,this.applyToPoint=S,this.applyToX=C,this.applyToY=w,this.applyToZ=T,this.applyToPointArray=A,this.applyToTriplePoints=k,this.applyToPointStringified=j,this.toCSS=M,this.to2dCSS=P,this.clone=b,this.cloneFromProps=x,this.equals=y,this.inversePoints=O,this.inversePoint=D,this.getInverseMatrix=E,this._t=this.transform,this.isIdentity=v,this._identity=!0,this._identityCalculated=!1,this.props=p(`float32`,16),this.reset()}}();function $e(e){"@babel/helpers - typeof";return $e=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},$e(e)}var V={},et=`__[STANDALONE]__`,tt=`__[ANIMATIONDATA]__`,nt=``;function rt(e){s(e)}function it(){et===!0?we.searchAnimations(tt,et,nt):we.searchAnimations()}function at(e){ae(e)}function ot(e){fe(e)}function st(e){return et===!0&&(e.animationData=JSON.parse(tt)),we.loadAnimation(e)}function ct(e){if(typeof e==`string`)switch(e){case`high`:ue(200);break;default:case`medium`:ue(50);break;case`low`:ue(10);break}else!isNaN(e)&&e>1&&ue(e)}function lt(){return typeof navigator<`u`}function ut(e,t){e===`expressions`&&se(t)}function dt(e){switch(e){case`propertyFactory`:return z;case`shapePropertyFactory`:return Qe;case`matrix`:return B;default:return null}}V.play=we.play,V.pause=we.pause,V.setLocationHref=rt,V.togglePause=we.togglePause,V.setSpeed=we.setSpeed,V.setDirection=we.setDirection,V.stop=we.stop,V.searchAnimations=it,V.registerAnimation=we.registerAnimation,V.loadAnimation=st,V.setSubframeRendering=at,V.resize=we.resize,V.goToAndStop=we.goToAndStop,V.destroy=we.destroy,V.setQuality=ct,V.inBrowser=lt,V.installPlugin=ut,V.freeze=we.freeze,V.unfreeze=we.unfreeze,V.setVolume=we.setVolume,V.mute=we.mute,V.unmute=we.unmute,V.getRegisteredAnimations=we.getRegisteredAnimations,V.useWebWorker=a,V.setIDPrefix=ot,V.__getFactory=dt,V.version=`5.13.0`;function ft(){document.readyState===`complete`&&(clearInterval(_t),it())}function pt(e){for(var t=mt.split(`&`),n=0;n=1?a.push({s:e-1,e:t-1}):(a.push({s:e,e:1}),a.push({s:0,e:t-1}));var o=[],s,c=a.length,l;for(s=0;sr+n)){var u=l.s*i<=r?0:(l.s*i-r)/n,d=l.e*i>=r+n?1:(l.e*i-r)/n;o.push([u,d])}return o.length||o.push([0,0]),o},bt.prototype.releasePathsData=function(e){var t,n=e.length;for(t=0;t1?1+r:this.s.v<0?0+r:this.s.v+r,n=this.e.v>1?1+r:this.e.v<0?0+r:this.e.v+r,t>n){var i=t;t=n,n=i}t=Math.round(t*1e4)*1e-4,n=Math.round(n*1e4)*1e-4,this.sValue=t,this.eValue=n}else t=this.sValue,n=this.eValue;var a,o,s=this.shapes.length,c,l,u,d,f,p=0;if(n===t)for(o=0;o=0;--o)if(h=this.shapes[o],h.shape._mdf){for(g=h.localShapeCollection,g.releaseShapes(),this.m===2&&s>1?(b=this.calculateShapeEdges(t,n,h.totalShapeLength,y,p),y+=h.totalShapeLength):b=[[_,v]],l=b.length,c=0;c=1?m.push({s:h.totalShapeLength*(_-1),e:h.totalShapeLength*(v-1)}):(m.push({s:h.totalShapeLength*_,e:h.totalShapeLength}),m.push({s:0,e:h.totalShapeLength*(v-1)}));var x=this.addShapes(h,m[0]);if(m[0].s!==m[0].e){if(m.length>1)if(h.shape.paths.shapes[h.shape.paths._length-1].c){var S=x.pop();this.addPaths(x,g),x=this.addShapes(h,m[1],S)}else this.addPaths(x,g),x=this.addShapes(h,m[1]);this.addPaths(x,g)}}h.shape.paths=g}}else if(this._mdf)for(o=0;ot.e){n.c=!1;break}else t.s<=l&&t.e>=l+u.addedLength?(this.addSegment(i[a].v[s-1],i[a].o[s-1],i[a].i[s],i[a].v[s],n,d,g),g=!1):(p=je.getNewSegment(i[a].v[s-1],i[a].v[s],i[a].o[s-1],i[a].i[s],(t.s-l)/u.addedLength,(t.e-l)/u.addedLength,f[s-1]),this.addSegmentFromArray(p,n,d,g),g=!1,n.c=!1),l+=u.addedLength,d+=1;if(i[a].c&&f.length){if(u=f[s-1],l<=t.e){var _=f[s-1].addedLength;t.s<=l&&t.e>=l+_?(this.addSegment(i[a].v[s-1],i[a].o[s-1],i[a].i[0],i[a].v[0],n,d,g),g=!1):(p=je.getNewSegment(i[a].v[s-1],i[a].v[0],i[a].o[s-1],i[a].i[0],(t.s-l)/_,(t.e-l)/_,f[s-1]),this.addSegmentFromArray(p,n,d,g),g=!1,n.c=!1)}else n.c=!1;l+=u.addedLength,d+=1}if(n._length&&(n.setXYAt(n.v[h][0],n.v[h][1],`i`,h),n.setXYAt(n.v[n._length-1][0],n.v[n._length-1][1],`o`,n._length-1)),l>t.e)break;a=this.p.keyframes[this.p.keyframes.length-1].t?(r=this.p.getValueAtTime(this.p.keyframes[this.p.keyframes.length-1].t/n,0),i=this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length-1].t-.05)/n,0)):(r=this.p.pv,i=this.p.getValueAtTime((this.p._caching.lastFrame+this.p.offsetTime-.01)/n,this.p.offsetTime));else if(this.px&&this.px.keyframes&&this.py.keyframes&&this.px.getValueAtTime&&this.py.getValueAtTime){r=[],i=[];var a=this.px,o=this.py;a._caching.lastFrame+a.offsetTime<=a.keyframes[0].t?(r[0]=a.getValueAtTime((a.keyframes[0].t+.01)/n,0),r[1]=o.getValueAtTime((o.keyframes[0].t+.01)/n,0),i[0]=a.getValueAtTime(a.keyframes[0].t/n,0),i[1]=o.getValueAtTime(o.keyframes[0].t/n,0)):a._caching.lastFrame+a.offsetTime>=a.keyframes[a.keyframes.length-1].t?(r[0]=a.getValueAtTime(a.keyframes[a.keyframes.length-1].t/n,0),r[1]=o.getValueAtTime(o.keyframes[o.keyframes.length-1].t/n,0),i[0]=a.getValueAtTime((a.keyframes[a.keyframes.length-1].t-.01)/n,0),i[1]=o.getValueAtTime((o.keyframes[o.keyframes.length-1].t-.01)/n,0)):(r=[a.pv,o.pv],i[0]=a.getValueAtTime((a._caching.lastFrame+a.offsetTime-.01)/n,a.offsetTime),i[1]=o.getValueAtTime((o._caching.lastFrame+o.offsetTime-.01)/n,o.offsetTime))}else i=e,r=i;this.v.rotate(-Math.atan2(r[1]-i[1],r[0]-i[0]))}this.data.p&&this.data.p.s?this.data.p.z?this.v.translate(this.px.v,this.py.v,-this.pz.v):this.v.translate(this.px.v,this.py.v,0):this.v.translate(this.p.v[0],this.p.v[1],-this.p.v[2])}this.frameId=this.elem.globalData.frameId}}function r(){if(this.appliedTransformations=0,this.pre.reset(),!this.a.effectsSequence.length)this.pre.translate(-this.a.v[0],-this.a.v[1],this.a.v[2]),this.appliedTransformations=1;else return;if(!this.s.effectsSequence.length)this.pre.scale(this.s.v[0],this.s.v[1],this.s.v[2]),this.appliedTransformations=2;else return;if(this.sk)if(!this.sk.effectsSequence.length&&!this.sa.effectsSequence.length)this.pre.skewFromAxis(-this.sk.v,this.sa.v),this.appliedTransformations=3;else return;this.r?this.r.effectsSequence.length||(this.pre.rotate(-this.r.v),this.appliedTransformations=4):!this.rz.effectsSequence.length&&!this.ry.effectsSequence.length&&!this.rx.effectsSequence.length&&!this.or.effectsSequence.length&&(this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]),this.appliedTransformations=4)}function i(){}function a(e){this._addDynamicProperty(e),this.elem.addDynamicProperty(e),this._isDirty=!0}function o(e,t,n){if(this.elem=e,this.frameId=-1,this.propType=`transform`,this.data=t,this.v=new B,this.pre=new B,this.appliedTransformations=0,this.initDynamicPropertyContainer(n||e),t.p&&t.p.s?(this.px=z.getProp(e,t.p.x,0,0,this),this.py=z.getProp(e,t.p.y,0,0,this),t.p.z&&(this.pz=z.getProp(e,t.p.z,0,0,this))):this.p=z.getProp(e,t.p||{k:[0,0,0]},1,0,this),t.rx){if(this.rx=z.getProp(e,t.rx,0,D,this),this.ry=z.getProp(e,t.ry,0,D,this),this.rz=z.getProp(e,t.rz,0,D,this),t.or.k[0].ti){var r,i=t.or.k.length;for(r=0;r0;)--n,this._elements.unshift(t[n]);this.dynamicProperties.length?this.k=!0:this.getValue(!0)},St.prototype.resetElements=function(e){var t,n=e.length;for(t=0;t0?Math.floor(f):Math.ceil(f),h=this.pMatrix.props,g=this.rMatrix.props,_=this.sMatrix.props;this.pMatrix.reset(),this.rMatrix.reset(),this.sMatrix.reset(),this.tMatrix.reset(),this.matrix.reset();var v=0;if(f>0){for(;vm;)this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!0),--v;p&&(this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,-p,!0),v-=p)}r=this.data.m===1?0:this._currentCopies-1,i=this.data.m===1?1:-1,a=this._currentCopies;for(var y,b;a;){if(t=this.elemsData[r].it,n=t[t.length-1].transform.mProps.v.props,b=n.length,t[t.length-1].transform.mProps._mdf=!0,t[t.length-1].transform.op._mdf=!0,t[t.length-1].transform.op.v=this._currentCopies===1?this.so.v:this.so.v+(this.eo.v-this.so.v)*(r/(this._currentCopies-1)),v!==0){for((r!==0&&i===1||r!==this._currentCopies-1&&i===-1)&&this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!1),this.matrix.transform(g[0],g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15]),this.matrix.transform(_[0],_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15]),this.matrix.transform(h[0],h[1],h[2],h[3],h[4],h[5],h[6],h[7],h[8],h[9],h[10],h[11],h[12],h[13],h[14],h[15]),y=0;y0&&r<1?[t]:[]:[t-r,t+r].filter(function(e){return e>0&&e<1})},At.prototype.split=function(e){if(e<=0)return[kt(this.points[0]),this];if(e>=1)return[this,kt(this.points[this.points.length-1])];var t=Et(this.points[0],this.points[1],e),n=Et(this.points[1],this.points[2],e),r=Et(this.points[2],this.points[3],e),i=Et(t,n,e),a=Et(n,r,e),o=Et(i,a,e);return[new At(this.points[0],t,i,o,!0),new At(o,a,r,this.points[3],!0)]};function jt(e,t){var n=e.points[0][t],r=e.points[e.points.length-1][t];if(n>r){var i=r;r=n,n=i}for(var a=Dt(3*e.a[t],2*e.b[t],e.c[t]),o=0;o0&&a[o]<1){var s=e.point(a[o])[t];sr&&(r=s)}return{min:n,max:r}}At.prototype.bounds=function(){return{x:jt(this,0),y:jt(this,1)}},At.prototype.boundingBox=function(){var e=this.bounds();return{left:e.x.min,right:e.x.max,top:e.y.min,bottom:e.y.max,width:e.x.max-e.x.min,height:e.y.max-e.y.min,cx:(e.x.max+e.x.min)/2,cy:(e.y.max+e.y.min)/2}};function Mt(e,t,n){var r=e.boundingBox();return{cx:r.cx,cy:r.cy,width:r.width,height:r.height,bez:e,t:(t+n)/2,t1:t,t2:n}}function Nt(e){var t=e.bez.split(.5);return[Mt(t[0],e.t1,e.t),Mt(t[1],e.t,e.t2)]}function Pt(e,t){return Math.abs(e.cx-t.cx)*2=a||e.width<=r&&e.height<=r&&t.width<=r&&t.height<=r){i.push([e.t,t.t]);return}var o=Nt(e),s=Nt(t);Ft(o[0],s[0],n+1,r,i,a),Ft(o[0],s[1],n+1,r,i,a),Ft(o[1],s[0],n+1,r,i,a),Ft(o[1],s[1],n+1,r,i,a)}}At.prototype.intersections=function(e,t,n){t===void 0&&(t=2),n===void 0&&(n=7);var r=[];return Ft(Mt(this,0,1),Mt(e,0,1),0,t,r,n),r},At.shapeSegment=function(e,t){var n=(t+1)%e.length();return new At(e.v[t],e.o[t],e.i[n],e.v[n],!0)},At.shapeSegmentInverted=function(e,t){var n=(t+1)%e.length();return new At(e.v[n],e.i[n],e.o[t],e.v[t],!0)};function It(e,t){return[e[1]*t[2]-e[2]*t[1],e[2]*t[0]-e[0]*t[2],e[0]*t[1]-e[1]*t[0]]}function W(e,t,n,r){var i=[e[0],e[1],1],a=[t[0],t[1],1],o=[n[0],n[1],1],s=[r[0],r[1],1],c=It(It(i,a),It(o,s));return wt(c[2])?null:[c[0]/c[2],c[1]/c[2]]}function G(e,t,n){return[e[0]+Math.cos(t)*n,e[1]-Math.sin(t)*n]}function Lt(e,t){return Math.hypot(e[0]-t[0],e[1]-t[1])}function Rt(e,t){return U(e[0],t[0])&&U(e[1],t[1])}function K(){}u([yt],K),K.prototype.initModifierProperties=function(e,t){this.getValue=this.processKeys,this.amplitude=z.getProp(e,t.s,0,null,this),this.frequency=z.getProp(e,t.r,0,null,this),this.pointsType=z.getProp(e,t.pt,0,null,this),this._isAnimated=this.amplitude.effectsSequence.length!==0||this.frequency.effectsSequence.length!==0||this.pointsType.effectsSequence.length!==0};function q(e,t,n,r,i,a,o){var s=n-Math.PI/2,c=n+Math.PI/2,l=t[0]+Math.cos(n)*r*i,u=t[1]-Math.sin(n)*r*i;e.setTripleAt(l,u,l+Math.cos(s)*a,u-Math.sin(s)*a,l+Math.cos(c)*o,u-Math.sin(c)*o,e.length())}function J(e,t){var n=[t[0]-e[0],t[1]-e[1]],r=-Math.PI*.5;return[Math.cos(r)*n[0]-Math.sin(r)*n[1],Math.sin(r)*n[0]+Math.cos(r)*n[1]]}function Y(e,t){var n=t===0?e.length()-1:t-1,r=(t+1)%e.length(),i=e.v[n],a=e.v[r],o=J(i,a);return Math.atan2(0,1)-Math.atan2(o[1],o[0])}function X(e,t,n,r,i,a,o){var s=Y(t,n),c=t.v[n%t._length],l=t.v[n===0?t._length-1:n-1],u=t.v[(n+1)%t._length],d=a===2?Math.sqrt((c[0]-l[0])**2+(c[1]-l[1])**2):0,f=a===2?Math.sqrt((c[0]-u[0])**2+(c[1]-u[1])**2):0;q(e,t.v[n%t._length],s,o,r,f/((i+1)*2),d/((i+1)*2),a)}function zt(e,t,n,r,i,a){for(var o=0;o1&&t.length>1&&(i=Ut(e[0],t[t.length-1]),i)?[[e[0].split(i[0])[0]],[t[t.length-1].split(i[1])[1]]]:[n,r]}function Gt(e){for(var t,n=1;n1&&(t=Wt(e[e.length-1],e[0]),e[e.length-1]=t[0],e[0]=t[1]),e}function Kt(e,t){var n=e.inflectionPoints(),r,i,a,o;if(n.length===0)return[Vt(e,t)];if(n.length===1||U(n[1],1))return a=e.split(n[0]),r=a[0],i=a[1],[Vt(r,t),Vt(i,t)];a=e.split(n[0]),r=a[0];var s=(n[1]-n[0])/(1-n[0]);return a=a[1].split(s),o=a[0],i=a[1],[Vt(r,t),Vt(o,t),Vt(i,t)]}function qt(){}u([yt],qt),qt.prototype.initModifierProperties=function(e,t){this.getValue=this.processKeys,this.amount=z.getProp(e,t.a,0,null,this),this.miterLimit=z.getProp(e,t.ml,0,null,this),this.lineJoin=t.lj,this._isAnimated=this.amount.effectsSequence.length!==0},qt.prototype.processPath=function(e,t,n,r){var i=Ye.newElement();i.c=e.c;var a=e.length();e.c||--a;var o,s,c,l=[];for(o=0;o=0;--o)c=At.shapeSegmentInverted(e,o),l.push(Kt(c,t));l=Gt(l);var u=null,d=null;for(o=0;o0&&(o=!1),o){var u=l(`style`);u.setAttribute(`f-forigin`,n[r].fOrigin),u.setAttribute(`f-origin`,n[r].origin),u.setAttribute(`f-family`,n[r].fFamily),u.type=`text/css`,u.innerText=`@font-face {font-family: `+n[r].fFamily+`; font-style: normal; src: url('`+n[r].fPath+`');}`,t.appendChild(u)}}else if(n[r].fOrigin===`g`||n[r].origin===1){for(s=document.querySelectorAll(`link[f-forigin="g"], link[f-origin="1"]`),c=0;c=55296&&n<=56319){var r=e.charCodeAt(1);r>=56320&&r<=57343&&(t=(n-55296)*1024+r-56320+65536)}return t}function S(e,t){var n=e.toString(16)+t.toString(16);return d.indexOf(n)!==-1}function C(e){return e===s}function w(e){return e===o}function T(e){var t=x(e);return t>=c&&t<=u}function E(e){return T(e.substr(0,2))&&T(e.substr(2,2))}function D(e){return t.indexOf(e)!==-1}function O(e,t){var o=x(e.substr(t,2));if(o!==n)return!1;var s=0;for(t+=2;s<5;){if(o=x(e.substr(t,2)),oa)return!1;s+=1,t+=2}return x(e.substr(t,2))===r}function k(){this.isLoaded=!0}var A=function(){this.fonts=[],this.chars=null,this.typekitLoaded=0,this.isLoaded=!1,this._warned=!1,this.initTime=Date.now(),this.setIsLoadedBinded=this.setIsLoaded.bind(this),this.checkLoadedFontsBinded=this.checkLoadedFonts.bind(this)};return A.isModifier=S,A.isZeroWidthJoiner=C,A.isFlagEmoji=E,A.isRegionalCode=T,A.isCombinedCharacter=D,A.isRegionalFlag=O,A.isVariationSelector=w,A.BLACK_FLAG_CODE_POINT=n,A.prototype={addChars:_,addFonts:g,getCharData:v,getFontByName:b,measureText:y,checkLoadedFonts:m,setIsLoaded:k},A}();function Xt(e){this.animationData=e}Xt.prototype.getProp=function(e){return this.animationData.slots&&this.animationData.slots[e.sid]?Object.assign(e,this.animationData.slots[e.sid].p):e};function Zt(e){return new Xt(e)}function Qt(){}Qt.prototype={initRenderable:function(){this.isInRange=!1,this.hidden=!1,this.isTransparent=!1,this.renderableComponents=[]},addRenderableComponent:function(e){this.renderableComponents.indexOf(e)===-1&&this.renderableComponents.push(e)},removeRenderableComponent:function(e){this.renderableComponents.indexOf(e)!==-1&&this.renderableComponents.splice(this.renderableComponents.indexOf(e),1)},prepareRenderableFrame:function(e){this.checkLayerLimits(e)},checkTransparency:function(){this.finalTransform.mProp.o.v<=0?!this.isTransparent&&this.globalData.renderConfig.hideOnTransparent&&(this.isTransparent=!0,this.hide()):this.isTransparent&&(this.isTransparent=!1,this.show())},checkLayerLimits:function(e){this.data.ip-this.data.st<=e&&this.data.op-this.data.st>e?this.isInRange!==!0&&(this.globalData._mdf=!0,this._mdf=!0,this.isInRange=!0,this.show()):this.isInRange!==!1&&(this.globalData._mdf=!0,this.isInRange=!1,this.hide())},renderRenderable:function(){var e,t=this.renderableComponents.length;for(e=0;e.1)&&this.audio.seek(this._currentTime/this.globalData.frameRate):(this.audio.play(),this.audio.seek(this._currentTime/this.globalData.frameRate),this._isPlaying=!0))},mn.prototype.show=function(){},mn.prototype.hide=function(){this.audio.pause(),this._isPlaying=!1},mn.prototype.pause=function(){this.audio.pause(),this._isPlaying=!1,this._canPlay=!1},mn.prototype.resume=function(){this._canPlay=!0},mn.prototype.setRate=function(e){this.audio.rate(e)},mn.prototype.volume=function(e){this._volumeMultiplier=e,this._previousVolume=e*this._volume,this.audio.volume(this._previousVolume)},mn.prototype.getBaseElement=function(){return null},mn.prototype.destroy=function(){},mn.prototype.sourceRectAtTime=function(){},mn.prototype.initExpressions=function(){};function hn(){}hn.prototype.checkLayers=function(e){var t,n=this.layers.length,r;for(this.completeLayers=!0,t=n-1;t>=0;--t)this.elements[t]||(r=this.layers[t],r.ip-r.st<=e-this.layers[t].st&&r.op-r.st>e-this.layers[t].st&&this.buildItem(t)),this.completeLayers=this.elements[t]?this.completeLayers:!1;this.checkPendingElements()},hn.prototype.createItem=function(e){switch(e.ty){case 2:return this.createImage(e);case 0:return this.createComp(e);case 1:return this.createSolid(e);case 3:return this.createNull(e);case 4:return this.createShape(e);case 5:return this.createText(e);case 6:return this.createAudio(e);case 13:return this.createCamera(e);case 15:return this.createFootage(e);default:return this.createNull(e)}},hn.prototype.createCamera=function(){throw Error(`You're using a 3d camera. Try the html renderer.`)},hn.prototype.createAudio=function(e){return new mn(e,this.globalData,this)},hn.prototype.createFootage=function(e){return new pn(e,this.globalData,this)},hn.prototype.buildAllItems=function(){var e,t=this.layers.length;for(e=0;e0&&(this.maskElement.setAttribute(`id`,p),this.element.maskedElement.setAttribute(b,`url(`+c()+`#`+p+`)`),r.appendChild(this.maskElement)),this.viewData.length&&this.element.addRenderableComponent(this)}vn.prototype.getMaskProperty=function(e){return this.viewData[e].prop},vn.prototype.renderFrame=function(e){var t=this.element.finalTransform.mat,n,r=this.masksProperties.length;for(n=0;n1&&(r+=` C`+t.o[i-1][0]+`,`+t.o[i-1][1]+` `+t.i[0][0]+`,`+t.i[0][1]+` `+t.v[0][0]+`,`+t.v[0][1]),n.lastPath!==r){var o=``;n.elem&&(t.c&&(o=e.inv?this.solidPath+r:r),n.elem.setAttribute(`d`,o)),n.lastPath=r}},vn.prototype.destroy=function(){this.element=null,this.globalData=null,this.maskElement=null,this.data=null,this.masksProperties=null};var yn=function(){var e={};e.createFilter=t,e.createAlphaToLuminanceFilter=n;function t(e,t){var n=L(`filter`);return n.setAttribute(`id`,e),t!==!0&&(n.setAttribute(`filterUnits`,`objectBoundingBox`),n.setAttribute(`x`,`0%`),n.setAttribute(`y`,`0%`),n.setAttribute(`width`,`100%`),n.setAttribute(`height`,`100%`)),n}function n(){var e=L(`feColorMatrix`);return e.setAttribute(`type`,`matrix`),e.setAttribute(`color-interpolation-filters`,`sRGB`),e.setAttribute(`values`,`0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1`),e}return e}(),bn=function(){var e={maskType:!0,svgLumaHidden:!0,offscreenCanvas:typeof OffscreenCanvas<`u`};return(/MSIE 10/i.test(navigator.userAgent)||/MSIE 9/i.test(navigator.userAgent)||/rv:11.0/i.test(navigator.userAgent)||/Edge\/\d./i.test(navigator.userAgent))&&(e.maskType=!1),/firefox/i.test(navigator.userAgent)&&(e.svgLumaHidden=!1),e}(),xn={},Z=`filter_result_`;function Sn(e){var t,n=`SourceGraphic`,r=e.data.ef?e.data.ef.length:0,i=te(),a=yn.createFilter(i,!0),o=0;this.filters=[];var s;for(t=0;t=0&&(n=this.shapeModifiers[e].processShapes(this._isFirstFrame),!n);--e);}},searchProcessedElement:function(e){for(var t=this.processedElements,n=0,r=t.length;n.01)return!1;n+=1}return!0},Ln.prototype.checkCollapsable=function(){if(this.o.length/2!=this.c.length/4)return!1;if(this.data.k.k[0].s)for(var e=0,t=this.data.k.k.length;e0;)c=r.transformers[g].mProps._mdf||c,--h,--g;if(c)for(h=f-r.styles[u].lvl,g=r.transformers.length-1;h>0;)m.multiply(r.transformers[g].mProps.v),--h,--g}else m=e;if(p=r.sh.paths,o=p._length,c){for(s=``,a=0;a=1?v=.99:v<=-1&&(v=-.99);var y=g*v,b=Math.cos(_+t.a.v)*y+a[0],x=Math.sin(_+t.a.v)*y+a[1];r.setAttribute(`fx`,b),r.setAttribute(`fy`,x),i&&!t.g._collapsable&&(t.of.setAttribute(`fx`,b),t.of.setAttribute(`fy`,x))}}}function u(e,t,n){var r=t.style,i=t.d;i&&(i._mdf||n)&&i.dashStr&&(r.pElem.setAttribute(`stroke-dasharray`,i.dashStr),r.pElem.setAttribute(`stroke-dashoffset`,i.dashoffset[0])),t.c&&(t.c._mdf||n)&&r.pElem.setAttribute(`stroke`,`rgb(`+C(t.c.v[0])+`,`+C(t.c.v[1])+`,`+C(t.c.v[2])+`)`),(t.o._mdf||n)&&r.pElem.setAttribute(`stroke-opacity`,t.o.v),(t.w._mdf||n)&&(r.pElem.setAttribute(`stroke-width`,t.w.v),r.msElem&&r.msElem.setAttribute(`stroke-width`,t.w.v))}return n}();function Wn(e,t,n){this.shapes=[],this.shapesData=e.shapes,this.stylesList=[],this.shapeModifiers=[],this.itemsData=[],this.processedElements=[],this.animatedContents=[],this.initElement(e,t,n),this.prevViewData=[]}u([dn,_n,Cn,On,wn,fn,Tn],Wn),Wn.prototype.initSecondaryElement=function(){},Wn.prototype.identityMatrix=new B,Wn.prototype.buildExpressionInterface=function(){},Wn.prototype.createContent=function(){this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,this.layerElement,0,[],!0),this.filterUniqueShapes()},Wn.prototype.filterUniqueShapes=function(){var e,t=this.shapes.length,n,r,i=this.stylesList.length,a,o=[],s=!1;for(r=0;r1&&s&&this.setShapesAsAnimated(o)}},Wn.prototype.setShapesAsAnimated=function(e){var t,n=e.length;for(t=0;t=0;--c){if(g=this.searchProcessedElement(e[c]),g?t[c]=n[g-1]:e[c]._render=o,e[c].ty===`fl`||e[c].ty===`st`||e[c].ty===`gf`||e[c].ty===`gs`||e[c].ty===`no`)g?t[c].style.closed=e[c].hd:t[c]=this.createStyleElement(e[c],i),e[c]._render&&t[c].style.pElem.parentNode!==r&&r.appendChild(t[c].style.pElem),f.push(t[c].style);else if(e[c].ty===`gr`){if(!g)t[c]=this.createGroupElement(e[c]);else for(d=t[c].it.length,u=0;u1,this.kf&&this.addEffect(this.getKeyframeValue.bind(this)),this.kf},Kn.prototype.addEffect=function(e){this.effectsSequence.push(e),this.elem.addDynamicProperty(this)},Kn.prototype.getValue=function(e){if(!((this.elem.globalData.frameId===this.frameId||!this.effectsSequence.length)&&!e)){this.currentData.t=this.data.d.k[this.keysIndex].s.t;var t=this.currentData,n=this.keysIndex;if(this.lock){this.setCurrentData(this.currentData);return}this.lock=!0,this._mdf=!1;var r,i=this.effectsSequence.length,a=e||this.data.d.k[this.keysIndex].s;for(r=0;rt);)n+=1;return this.keysIndex!==n&&(this.keysIndex=n),this.data.d.k[this.keysIndex].s},Kn.prototype.buildFinalText=function(e){for(var t=[],n=0,r=e.length,i,a,o=!1,s=!1,c=``;n=55296&&i<=56319?Yt.isRegionalFlag(e,n)?c=e.substr(n,14):(a=e.charCodeAt(n+1),a>=56320&&a<=57343&&(Yt.isModifier(i,a)?(c=e.substr(n,2),o=!0):c=Yt.isFlagEmoji(e.substr(n,4))?e.substr(n,4):e.substr(n,2))):i>56319?(a=e.charCodeAt(n+1),Yt.isVariationSelector(i)&&(o=!0)):Yt.isZeroWidthJoiner(i)&&(o=!0,s=!0),o?(t[t.length-1]+=c,o=!1):t.push(c),n+=c.length;return t},Kn.prototype.completeTextData=function(e){e.__complete=!0;var t=this.elem.globalData.fontManager,n=this.data,r=[],i,a,o,s=0,c,l=n.m.g,u=0,d=0,f=0,p=[],m=0,h=0,g,_,v=t.getFontByName(e.f),y,b=0,x=Jt(v);e.fWeight=x.weight,e.fStyle=x.style,e.finalSize=e.s,e.finalText=this.buildFinalText(e.t),a=e.finalText.length,e.finalLineHeight=e.lh;var S=e.tr/1e3*e.finalSize,C;if(e.sz)for(var w=!0,T=e.sz[0],E=e.sz[1],D,O;w;){O=this.buildFinalText(e.t),D=0,m=0,a=O.length,S=e.tr/1e3*e.finalSize;var k=-1;for(i=0;iT&&O[i]!==` `?(k===-1?a+=1:i=k,D+=e.finalLineHeight||e.finalSize*1.2,O.splice(i,+(k===i),`\r`),k=-1,m=0):(m+=b,m+=S);D+=v.ascent*e.finalSize/100,this.canResize&&e.finalSize>this.minimumFontSize&&Eh?m:h,m=-2*S,c=``,o=!0,f+=1):c=j,t.chars?(y=t.getCharData(j,v.fStyle,t.getFontByName(e.f).fFamily),b=o?0:y.w*e.finalSize/100):b=t.measureText(c,e.f,e.finalSize),j===` `?A+=b+S:(m+=b+S+A,A=0),r.push({l:b,an:b,add:u,n:o,anIndexes:[],val:c,line:f,animatorJustifyOffset:0}),l==2){if(u+=b,c===``||c===` `||i===a-1){for((c===``||c===` `)&&(u-=b);d<=i;)r[d].an=u,r[d].ind=s,r[d].extra=b,d+=1;s+=1,u=0}}else if(l==3){if(u+=b,c===``||i===a-1){for(c===``&&(u-=b);d<=i;)r[d].an=u,r[d].ind=s,r[d].extra=b,d+=1;u=0,s+=1}}else r[s].ind=s,r[s].extra=0,s+=1;if(e.l=r,h=m>h?m:h,p.push(m),e.sz)e.boxWidth=e.sz[0],e.justifyOffset=0;else switch(e.boxWidth=h,e.j){case 1:e.justifyOffset=-e.boxWidth;break;case 2:e.justifyOffset=-e.boxWidth/2;break;default:e.justifyOffset=0}e.lineWidths=p;var M=n.a,N,P;_=M.length;var ee,te,F=[];for(g=0;g<_;g+=1){for(N=M[g],N.a.sc&&(e.strokeColorAnim=!0),N.a.sw&&(e.strokeWidthAnim=!0),(N.a.fc||N.a.fh||N.a.fs||N.a.fb)&&(e.fillColorAnim=!0),te=0,ee=N.s.b,i=0;i0?i=this.ne.v/100:a=-this.ne.v/100,this.xe.v>0?o=1-this.xe.v/100:s=1+this.xe.v/100;var c=Te.getBezierEasing(i,a,o,s).get,l=0,u=this.finalS,d=this.finalE,f=this.data.sh;if(f===2)l=d===u?+(r>=d):e(0,t(.5/(d-u)+(r-u)/(d-u),1)),l=c(l);else if(f===3)l=d===u?r>=d?0:1:1-e(0,t(.5/(d-u)+(r-u)/(d-u),1)),l=c(l);else if(f===4)d===u?l=0:(l=e(0,t(.5/(d-u)+(r-u)/(d-u),1)),l<.5?l*=2:l=1-2*(l-.5)),l=c(l);else if(f===5){if(d===u)l=0;else{var p=d-u;r=t(e(0,r+.5-u),d-u);var m=-p/2+r,h=p/2;l=Math.sqrt(1-m*m/(h*h))}l=c(l)}else f===6?(d===u?l=0:(r=t(e(0,r+.5-u),d-u),l=(1+Math.cos(Math.PI+Math.PI*2*r/(d-u)))/2),l=c(l)):(r>=n(u)&&(l=r-u<0?e(0,t(t(d,1)-(u-r),1)):e(0,t(d-r,1))),l=c(l));if(this.sm.v!==100){var g=this.sm.v*.01;g===0&&(g=1e-8);var _=.5-g*.5;l<_?l=0:(l=(l-_)/g,l>1&&(l=1))}return l*this.a.v},getValue:function(e){this.iterateDynamicProperties(),this._mdf=e||this._mdf,this._currentTextLength=this.elem.textProperty.currentData.l.length||0,e&&this.data.r===2&&(this.e.v=this._currentTextLength);var t=this.data.r===2?1:100/this.data.totalChars,n=this.o.v/t,r=this.s.v/t+n,i=this.e.v/t+n;if(r>i){var a=r;r=i,i=a}this.finalS=r,this.finalE=i}},u([Ke],r);function i(e,t,n){return new r(e,t,n)}return{getTextSelectorProp:i}}();function Jn(e,t,n){var r={propType:!1},i=z.getProp,a=t.a;this.a={r:a.r?i(e,a.r,0,D,n):r,rx:a.rx?i(e,a.rx,0,D,n):r,ry:a.ry?i(e,a.ry,0,D,n):r,sk:a.sk?i(e,a.sk,0,D,n):r,sa:a.sa?i(e,a.sa,0,D,n):r,s:a.s?i(e,a.s,1,.01,n):r,a:a.a?i(e,a.a,1,0,n):r,o:a.o?i(e,a.o,0,.01,n):r,p:a.p?i(e,a.p,1,0,n):r,sw:a.sw?i(e,a.sw,0,0,n):r,sc:a.sc?i(e,a.sc,1,0,n):r,fc:a.fc?i(e,a.fc,1,0,n):r,fh:a.fh?i(e,a.fh,0,0,n):r,fs:a.fs?i(e,a.fs,0,.01,n):r,fb:a.fb?i(e,a.fb,0,.01,n):r,t:a.t?i(e,a.t,0,0,n):r},this.s=qn.getTextSelectorProp(e,t.s,n),this.s.t=t.s.t}function Yn(e,t,n){this._isFirstFrame=!0,this._hasMaskedPath=!1,this._frameId=-1,this._textData=e,this._renderType=t,this._elem=n,this._animatorsData=m(this._textData.a.length),this._pathData={},this._moreOptions={alignment:{}},this.renderedLetters=[],this.lettersChangedFlag=!1,this.initDynamicPropertyContainer(n)}Yn.prototype.searchProperties=function(){var e,t=this._textData.a.length,n,r=z.getProp;for(e=0;e=m+Ee||!x?(T=(m+Ee-g)/h.partialLength,oe=b.point[0]+(h.point[0]-b.point[0])*T,se=b.point[1]+(h.point[1]-b.point[1])*T,a.translate(-n[0]*f[u].an*.005,-(n[1]*A)*.01),_=!1):x&&(g+=h.partialLength,v+=1,v>=x.length&&(v=0,y+=1,S[y]?x=S[y].points:D.v.c?(v=0,y=0,x=S[y].points):(g-=h.partialLength,x=null)),x&&(b=h,h=x[v],C=h.partialLength));ae=f[u].an/2-f[u].add,a.translate(-ae,0,0)}else ae=f[u].an/2-f[u].add,a.translate(-ae,0,0),a.translate(-n[0]*f[u].an*.005,-n[1]*A*.01,0);for(P=0;Pe?this.textSpans[e].span:L(s?`g`:`text`),b<=e){if(c.setAttribute(`stroke-linecap`,`butt`),c.setAttribute(`stroke-linejoin`,`round`),c.setAttribute(`stroke-miterlimit`,`4`),this.textSpans[e].span=c,s){var S=L(`g`);c.appendChild(S),this.textSpans[e].childSpan=S}this.textSpans[e].span=c,this.layerElement.appendChild(c)}c.style.display=`inherit`}if(l.reset(),d&&(o[e].n&&(f=-g,p+=n.yOffset,p+=+!!h,h=!1),this.applyTextPropertiesToMatrix(n,l,o[e].line,f,p),f+=o[e].l||0,f+=g),s){x=this.globalData.fontManager.getCharData(n.finalText[e],r.fStyle,this.globalData.fontManager.getFontByName(n.f).fFamily);var C;if(x.t===1)C=new rr(x.data,this.globalData,this);else{var w=Zn;x.data&&x.data.shapes&&(w=this.buildShapeData(x.data,n.finalSize)),C=new Wn(w,this.globalData,this)}if(this.textSpans[e].glyph){var T=this.textSpans[e].glyph;this.textSpans[e].childSpan.removeChild(T.layerElement),T.destroy()}this.textSpans[e].glyph=C,C._debug=!0,C.prepareFrame(0),C.renderFrame(),this.textSpans[e].childSpan.appendChild(C.layerElement),x.t===1&&this.textSpans[e].childSpan.setAttribute(`transform`,`scale(`+n.finalSize/100+`,`+n.finalSize/100+`)`)}else d&&c.setAttribute(`transform`,`translate(`+l.props[12]+`,`+l.props[13]+`)`),c.textContent=o[e].val,c.setAttributeNS(`http://www.w3.org/XML/1998/namespace`,`xml:space`,`preserve`)}d&&c&&c.setAttribute(`d`,u)}for(;e=0;--t)(this.completeLayers||this.elements[t])&&this.elements[t].prepareFrame(e-this.layers[t].st);if(this.globalData._mdf)for(t=0;t=0;--n)(this.completeLayers||this.elements[n])&&(this.elements[n].prepareFrame(this.renderedFrame-this.layers[n].st),this.elements[n]._mdf&&(this._mdf=!0))}},nr.prototype.renderInnerContent=function(){var e,t=this.layers.length;for(e=0;e=0;--n)e.finalTransform.multiply(e.transforms[n].transform.mProps.v);e._mdf=i},processSequences:function(e){var t,n=this.sequenceList.length;for(t=0;t=1){this.buffers=[];var e=this.globalData.canvasContext,t=cr.createCanvas(e.canvas.width,e.canvas.height);this.buffers.push(t);var n=cr.createCanvas(e.canvas.width,e.canvas.height);this.buffers.push(n),this.data.tt>=3&&!document._isProxy&&cr.loadLumaCanvas()}this.canvasContext=this.globalData.canvasContext,this.transformCanvas=this.globalData.transformCanvas,this.renderableEffectsManager=new ur(this),this.searchEffectTransforms()},createContent:function(){},setBlendMode:function(){var e=this.globalData;if(e.blendMode!==this.data.bm){e.blendMode=this.data.bm;var t=$t(this.data.bm);e.canvasContext.globalCompositeOperation=t}},createRenderableComponents:function(){this.maskManager=new dr(this.data,this),this.transformEffects=this.renderableEffectsManager.getEffects(gn.TRANSFORM_EFFECT)},hideElement:function(){!this.hidden&&(!this.isInRange||this.isTransparent)&&(this.hidden=!0)},showElement:function(){this.isInRange&&!this.isTransparent&&(this.hidden=!1,this._isFirstFrame=!0,this.maskManager._isFirstFrame=!0)},clearCanvas:function(e){e.clearRect(this.transformCanvas.tx,this.transformCanvas.ty,this.transformCanvas.w*this.transformCanvas.sx,this.transformCanvas.h*this.transformCanvas.sy)},prepareLayer:function(){if(this.data.tt>=1){var e=this.buffers[0].getContext(`2d`);this.clearCanvas(e),e.drawImage(this.canvasContext.canvas,0,0),this.currentTransform=this.canvasContext.getTransform(),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform)}},exitLayer:function(){if(this.data.tt>=1){var e=this.buffers[1],t=e.getContext(`2d`);if(this.clearCanvas(t),t.drawImage(this.canvasContext.canvas,0,0),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform),this.comp.getElementById(`tp`in this.data?this.data.tp:this.data.ind-1).renderFrame(!0),this.canvasContext.setTransform(1,0,0,1,0,0),this.data.tt>=3&&!document._isProxy){var n=cr.getLumaCanvas(this.canvasContext.canvas);n.getContext(`2d`).drawImage(this.canvasContext.canvas,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.drawImage(n,0,0)}this.canvasContext.globalCompositeOperation=pr[this.data.tt],this.canvasContext.drawImage(e,0,0),this.canvasContext.globalCompositeOperation=`destination-over`,this.canvasContext.drawImage(this.buffers[0],0,0),this.canvasContext.setTransform(this.currentTransform),this.canvasContext.globalCompositeOperation=`source-over`}},renderFrame:function(e){if(!(this.hidden||this.data.hd)&&!(this.data.td===1&&!e)){this.renderTransform(),this.renderRenderable(),this.renderLocalTransform(),this.setBlendMode();var t=this.data.ty===0;this.prepareLayer(),this.globalData.renderer.save(t),this.globalData.renderer.ctxTransform(this.finalTransform.localMat.props),this.globalData.renderer.ctxOpacity(this.finalTransform.localOpacity),this.renderInnerContent(),this.globalData.renderer.restore(t),this.exitLayer(),this.maskManager.hasMasks&&this.globalData.renderer.restore(!0),this._isFirstFrame&&=!1}},destroy:function(){this.canvasContext=null,this.data=null,this.globalData=null,this.maskManager.destroy()},mHelper:new B},fr.prototype.hide=fr.prototype.hideElement,fr.prototype.show=fr.prototype.showElement;function mr(e,t,n,r){this.styledShapes=[],this.tr=[0,0,0,0,0,0];var i=4;t.ty===`rc`?i=5:t.ty===`el`?i=6:t.ty===`sr`&&(i=7),this.sh=Qe.getShapeProp(e,t,i,e);var a,o=n.length,s;for(a=0;a=0;--a){if(d=this.searchProcessedElement(e[a]),d?t[a]=n[d-1]:e[a]._shouldRender=r,e[a].ty===`fl`||e[a].ty===`st`||e[a].ty===`gf`||e[a].ty===`gs`)d?t[a].style.closed=!1:t[a]=this.createStyleElement(e[a],m),l.push(t[a].style);else if(e[a].ty===`gr`){if(!d)t[a]=this.createGroupElement(e[a]);else for(c=t[a].it.length,s=0;s=0;--i)t[i].ty===`tr`?(o=n[i].transform,this.renderShapeTransform(e,o)):t[i].ty===`sh`||t[i].ty===`el`||t[i].ty===`rc`||t[i].ty===`sr`?this.renderPath(t[i],n[i]):t[i].ty===`fl`?this.renderFill(t[i],n[i],o):t[i].ty===`st`?this.renderStroke(t[i],n[i],o):t[i].ty===`gf`||t[i].ty===`gs`?this.renderGradientFill(t[i],n[i],o):t[i].ty===`gr`?this.renderShape(o,t[i].it,n[i].it):t[i].ty;r&&this.drawLayer()},hr.prototype.renderStyledShape=function(e,t){if(this._isFirstFrame||t._mdf||e.transforms._mdf){var n=e.trNodes,r=t.paths,i,a,o,s=r._length;n.length=0;var c=e.transforms.finalTransform;for(o=0;o=1?u=.99:u<=-1&&(u=-.99);var d=c*u,f=Math.cos(l+t.a.v)*d+o[0],p=Math.sin(l+t.a.v)*d+o[1];i=a.createRadialGradient(f,p,0,o[0],o[1],c)}var m,h=e.g.p,g=t.g.c,_=1;for(m=0;ma&&c===`xMidYMid slice`||ii&&s===`meet`||ai&&s===`slice`)?this.transformCanvas.tx=(n-this.transformCanvas.w*(r/this.transformCanvas.h))/2*this.renderConfig.dpr:l===`xMax`&&(ai&&s===`slice`)?this.transformCanvas.tx=(n-this.transformCanvas.w*(r/this.transformCanvas.h))*this.renderConfig.dpr:this.transformCanvas.tx=0,u===`YMid`&&(a>i&&s===`meet`||ai&&s===`meet`||a=0;--e)this.elements[e]&&this.elements[e].destroy&&this.elements[e].destroy();this.elements.length=0,this.globalData.canvasContext=null,this.animationItem.container=null,this.destroyed=!0},Q.prototype.renderFrame=function(e,t){if(!(this.renderedFrame===e&&this.renderConfig.clearCanvas===!0&&!t||this.destroyed||e===-1)){this.renderedFrame=e,this.globalData.frameNum=e-this.animationItem._isFirstFrame,this.globalData.frameId+=1,this.globalData._mdf=!this.renderConfig.clearCanvas||t,this.globalData.projectInterface.currentFrame=e;var n,r=this.layers.length;for(this.completeLayers||this.checkLayers(e),n=r-1;n>=0;--n)(this.completeLayers||this.elements[n])&&this.elements[n].prepareFrame(e-this.layers[n].st);if(this.globalData._mdf){for(this.renderConfig.clearCanvas===!0?this.canvasContext.clearRect(0,0,this.transformCanvas.w,this.transformCanvas.h):this.save(),n=r-1;n>=0;--n)(this.completeLayers||this.elements[n])&&this.elements[n].renderFrame();this.renderConfig.clearCanvas!==!0&&this.restore()}}},Q.prototype.buildItem=function(e){var t=this.elements;if(!(t[e]||this.layers[e].ty===99)){var n=this.createItem(this.layers[e],this,this.globalData);t[e]=n,n.initExpressions()}},Q.prototype.checkPendingElements=function(){for(;this.pendingElements.length;)this.pendingElements.pop().checkParenting()},Q.prototype.hide=function(){this.animationItem.container.style.display=`none`},Q.prototype.show=function(){this.animationItem.container.style.display=`block`};function yr(){this.opacity=-1,this.transform=p(`float32`,16),this.fillStyle=``,this.strokeStyle=``,this.lineWidth=``,this.lineCap=``,this.lineJoin=``,this.miterLimit=``,this.id=Math.random()}function br(){this.stack=[],this.cArrPos=0,this.cTr=new B;var e,t=15;for(e=0;e=0;--t)(this.completeLayers||this.elements[t])&&this.elements[t].renderFrame()},xr.prototype.destroy=function(){var e;for(e=this.layers.length-1;e>=0;--e)this.elements[e]&&this.elements[e].destroy();this.layers=null,this.elements=null},xr.prototype.createComp=function(e){return new xr(e,this.globalData,this)};function Sr(e,t){this.animationItem=e,this.renderConfig={clearCanvas:t&&t.clearCanvas!==void 0?t.clearCanvas:!0,context:t&&t.context||null,progressiveLoad:t&&t.progressiveLoad||!1,preserveAspectRatio:t&&t.preserveAspectRatio||`xMidYMid meet`,imagePreserveAspectRatio:t&&t.imagePreserveAspectRatio||`xMidYMid slice`,contentVisibility:t&&t.contentVisibility||`visible`,className:t&&t.className||``,id:t&&t.id||``,runExpressions:!t||t.runExpressions===void 0||t.runExpressions},this.renderConfig.dpr=t&&t.dpr||1,this.animationItem.wrapper&&(this.renderConfig.dpr=t&&t.dpr||window.devicePixelRatio||1),this.renderedFrame=-1,this.globalData={frameNum:-1,_mdf:!1,renderConfig:this.renderConfig,currentGlobalAlpha:-1},this.contextData=new br,this.elements=[],this.pendingElements=[],this.transformMat=new B,this.completeLayers=!1,this.rendererType=`canvas`,this.renderConfig.clearCanvas&&(this.ctxTransform=this.contextData.transform.bind(this.contextData),this.ctxOpacity=this.contextData.opacity.bind(this.contextData),this.ctxFillStyle=this.contextData.fillStyle.bind(this.contextData),this.ctxStrokeStyle=this.contextData.strokeStyle.bind(this.contextData),this.ctxLineWidth=this.contextData.lineWidth.bind(this.contextData),this.ctxLineCap=this.contextData.lineCap.bind(this.contextData),this.ctxLineJoin=this.contextData.lineJoin.bind(this.contextData),this.ctxMiterLimit=this.contextData.miterLimit.bind(this.contextData),this.ctxFill=this.contextData.fill.bind(this.contextData),this.ctxFillRect=this.contextData.fillRect.bind(this.contextData),this.ctxStroke=this.contextData.stroke.bind(this.contextData),this.save=this.contextData.save.bind(this.contextData))}return u([Q],Sr),Sr.prototype.createComp=function(e){return new xr(e,this.globalData,this)},be(`canvas`,Sr),vt.registerModifier(`tm`,bt),vt.registerModifier(`pb`,xt),vt.registerModifier(`rp`,St),vt.registerModifier(`rd`,Ct),vt.registerModifier(`zz`,K),vt.registerModifier(`op`,qt),V}))}))(),1);function Cr({documentID:e,className:t=``,showError:n=!0}){let r=(0,g.useRef)(null),i=(0,g.useRef)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(null);return(0,g.useEffect)(()=>{let t=!1,n=null;return o(``),c(null),fetch(k.stickerDocumentAnimationURL(e),{credentials:`same-origin`}).then(async e=>{if(!e.ok){let t=await e.json().catch(()=>null);throw Error(t?.error||e.statusText)}if((e.headers.get(`content-type`)??``).includes(`json`)){let n=await e.json();if(t||!r.current)return;i.current?.destroy(),i.current=Sr.default.loadAnimation({container:r.current,renderer:`canvas`,loop:!0,autoplay:!0,animationData:n});return}let a=await e.blob();t||(n=URL.createObjectURL(a),c(n))}).catch(e=>{t||o(O(e))}),()=>{t=!0,i.current?.destroy(),i.current=null,n&&URL.revokeObjectURL(n)}},[e]),(0,W.jsxs)(`div`,{className:`sticker-doc-cell ${t}`.trim(),children:[s?(0,W.jsx)(`img`,{className:`sticker-doc-image`,src:s,alt:``}):(0,W.jsx)(`div`,{className:`sticker-doc-canvas`,ref:r}),a&&n&&(0,W.jsx)(`span`,{className:`sticker-doc-error`,children:a})]})}function wr({kind:e,onClose:t,onCreated:n}){let r=e===`emoji`?`emoji`:`sticker`,[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(``),[u,d]=(0,g.useState)(null),[f,p]=(0,g.useState)(``),[m,h]=(0,g.useState)(!1),[_,v]=(0,g.useState)(``);async function y(){if(!i.trim()||!o.trim()||!c.trim()||!u){v(`Title, short name, emoji and a first ${r} file are required.`);return}if(!f.trim()){v(`Please enter an operation reason`);return}h(!0),v(``);try{let r=new FormData;r.set(`metadata`,JSON.stringify({command_id:``,reason:f.trim(),confirm:!0,title:i.trim(),short_name:o.trim().toLowerCase(),kind:e,emoji:c.trim()})),r.set(`file`,u,u.name),await k.createStickerSet(r),n(),t()}catch(e){v(O(e))}finally{h(!1)}}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Create a new ${r} pack`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`New set`}),(0,W.jsx)(`h2`,{children:`Create a new ${r} pack`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:t,disabled:m,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsxs)(`div`,{className:`gift-fields-grid`,children:[(0,W.jsxs)(`label`,{children:[(0,W.jsx)(`span`,{children:`Title`}),(0,W.jsx)(`input`,{value:i,maxLength:64,onChange:e=>a(e.target.value)})]}),(0,W.jsxs)(`label`,{children:[(0,W.jsx)(`span`,{children:`Short name`}),(0,W.jsx)(`input`,{value:o,maxLength:32,onChange:e=>s(e.target.value),placeholder:`lowercase_short_name`})]}),(0,W.jsxs)(`label`,{children:[(0,W.jsx)(`span`,{children:`Emoji`}),(0,W.jsx)(`input`,{value:c,onChange:e=>l(e.target.value),placeholder:`e.g. 😀`})]})]}),(0,W.jsxs)(`label`,{className:`gift-file-picker ${u?`has-file`:``}`,children:[(0,W.jsx)(`input`,{type:`file`,accept:`.tgs,.json,.webp,application/json,application/x-tgsticker,image/webp`,onChange:e=>d(e.target.files?.[0]??null)}),(0,W.jsxs)(`span`,{className:`gift-file-copy`,children:[(0,W.jsx)(`span`,{className:`gift-field-label`,children:`First ${r}`}),(0,W.jsx)(`strong`,{children:u?u.name:`Choose a TGS, Lottie JSON, or WebP file`})]}),(0,W.jsx)(`span`,{className:`gift-file-action`,children:u?`Change file`:`Choose file`})]}),(0,W.jsxs)(`label`,{className:`gift-reason-field`,children:[(0,W.jsx)(`span`,{children:`Audit reason`}),(0,W.jsx)(`input`,{value:f,placeholder:`Briefly describe why this gift is being imported`,onChange:e=>p(e.target.value)})]}),_&&(0,W.jsx)(q,{children:_})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:t,disabled:m,children:`Close`}),(0,W.jsxs)(`button`,{className:`btn primary`,type:`button`,onClick:y,disabled:m,children:[m?(0,W.jsx)(I,{className:`spin`,size:15}):(0,W.jsx)(ht,{size:15}),`Create ${r} pack`]})]})]})}),document.body)}var Tr=24;function Er({set:e,onClose:t}){let n=e.Kind===`emoji`?`emoji`:`sticker`,[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(1),l=(0,g.useCallback)(()=>{let t=!1;return o(``),k.stickerSetDocuments(e.ID).then(e=>{t||i(e.document_ids??[])}).catch(e=>{t||o(O(e))}),()=>{t=!0}},[e.ID]);(0,g.useEffect)(()=>(i(null),c(1),l()),[l]);let u=r?.length??0,d=Math.max(1,Math.ceil(u/Tr)),f=Math.min(s,d),p=(f-1)*Tr,m=r?.slice(p,p+Tr)??[],h=m.length===0?0:p+1,_=h===0?0:h+m.length-1;return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal sticker-preview-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":e.Title||`#${e.ID}`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Set contents`}),(0,W.jsx)(`h2`,{children:e.Title||`#${e.ID}`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:t,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsx)(Dr,{setID:e.ID,noun:n,onAdded:l}),a&&(0,W.jsx)(q,{children:a}),!a&&r===null&&(0,W.jsxs)(`div`,{className:`loading-line`,children:[(0,W.jsx)(I,{className:`spin`,size:18}),` `,`Loading`]}),r!==null&&u===0&&!a&&(0,W.jsx)(`div`,{className:`empty-panel`,children:`This set has no documents.`}),m.length>0&&(0,W.jsx)(`div`,{className:`sticker-doc-grid`,children:m.map(t=>(0,W.jsxs)(`div`,{className:`sticker-doc-grid-cell`,children:[(0,W.jsx)(Cr,{documentID:t}),(0,W.jsx)(Z,{compact:!0,tone:`danger`,label:`Remove`,icon:(0,W.jsx)(pt,{size:12}),path:`/api/actions/remove-sticker-from-set`,payload:()=>({set_id:e.ID,document_id:t}),onDone:l})]},t))},f),u>Tr&&(0,W.jsxs)(`div`,{className:`gift-pager`,children:[(0,W.jsx)(`span`,{className:`gift-pager-range`,children:`Showing ${h}-${_} of ${u}`}),(0,W.jsxs)(`div`,{className:`gift-pager-controls`,children:[(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>c(e=>Math.max(1,e-1)),disabled:f<=1,children:[(0,W.jsx)(xe,{size:14}),` `,`Previous`]}),(0,W.jsx)(`span`,{className:`gift-pager-page`,children:`Page ${f} of ${d}`}),(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>c(e=>Math.min(d,e+1)),disabled:f>=d,children:[`Next`,` `,(0,W.jsx)(Se,{size:14})]})]})]})]})]})}),document.body)}function Dr({setID:e,noun:t,onAdded:n}){let[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(!1),[d,f]=(0,g.useState)(``);async function p(){if(!r){f(`Choose a ${t} file first`);return}if(!a.trim()){f(`An emoji is required.`);return}if(!s.trim()){f(`Please enter an operation reason`);return}u(!0),f(``);try{let t=new FormData;t.set(`metadata`,JSON.stringify({command_id:``,reason:s.trim(),confirm:!0,set_id:e,emoji:a.trim()})),t.set(`file`,r,r.name),await k.addStickerToSet(t),i(null),o(``),c(``),n()}catch(e){f(O(e))}finally{u(!1)}}return(0,W.jsxs)(`div`,{className:`sticker-add-form`,children:[(0,W.jsxs)(`label`,{className:`gift-file-picker compact ${r?`has-file`:``}`,children:[(0,W.jsx)(`input`,{type:`file`,accept:`.tgs,.json,.webp,application/json,application/x-tgsticker,image/webp`,onChange:e=>i(e.target.files?.[0]??null)}),(0,W.jsx)(`span`,{className:`gift-file-copy`,children:(0,W.jsx)(`strong`,{children:r?r.name:`Choose a TGS, Lottie JSON, or WebP file`})})]}),(0,W.jsx)(`input`,{className:`small-input`,value:a,onChange:e=>o(e.target.value),placeholder:`e.g. 😀`}),(0,W.jsx)(`input`,{className:`small-input`,value:s,onChange:e=>c(e.target.value),placeholder:`Describe why this operation is being performed`}),(0,W.jsxs)(`button`,{className:`btn primary compact-btn`,type:`button`,onClick:p,disabled:l,children:[l?(0,W.jsx)(I,{className:`spin`,size:14}):(0,W.jsx)(Ye,{size:14}),` `,`Add ${t}`]}),d&&(0,W.jsx)(`span`,{className:`sticker-add-form-error`,children:d})]})}function Or({kind:e}){let[t,n]=(0,g.useState)([]),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(!1),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(10),[d,f]=(0,g.useState)(1),[p,m]=(0,g.useState)({}),[h,_]=(0,g.useState)({}),[v,y]=(0,g.useState)(null),[b,x]=(0,g.useState)(!1),S=e===`emoji`?`Emoji`:`Stickers`,C=e===`emoji`?`Custom-emoji packs — system packs aren't shown here, they're not hand-edited`:`Sticker packs — system packs (dice, animated emoji, gifts) aren't shown here, they're not hand-edited`,w=e===`emoji`?`emoji`:`sticker`;async function T(){o(!0),c(``);try{n((await k.stickerSets(e)).rows??[])}catch(e){c(O(e))}finally{o(!1)}}(0,g.useEffect)(()=>{T()},[e]);let E=(0,g.useMemo)(()=>{let e=r.trim().toLowerCase();return e?t.filter(t=>String(t.ID).includes(e)||t.ShortName.toLowerCase().includes(e)||t.Title.toLowerCase().includes(e)):t},[t,r]);(0,g.useEffect)(()=>{f(1)},[r,l,e]);let D=l===`all`?1:Math.max(1,Math.ceil(E.length/l)),A=Math.min(d,D),j=(0,g.useMemo)(()=>{if(l===`all`)return E;let e=(A-1)*l;return E.slice(e,e+l)},[E,A,l]),M=j.length===0?0:l===`all`?1:(A-1)*l+1,N=M===0?0:M+j.length-1,P=(0,g.useMemo)(()=>({total:t.length,official:t.filter(e=>e.Official).length,archived:t.filter(e=>e.Archived).length}),[t]);return(0,W.jsxs)(G,{title:S,eyebrow:C,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn`,type:`button`,onClick:()=>T(),disabled:a,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]}),(0,W.jsxs)(`button`,{className:`btn primary`,type:`button`,onClick:()=>x(!0),children:[(0,W.jsx)(Ye,{size:15}),` `,`Create ${w} pack`]})]}),children:[s&&(0,W.jsx)(q,{children:s}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Total sets`,value:String(P.total)}),(0,W.jsx)(Y,{label:`Official`,value:String(P.official),tone:`good`}),(0,W.jsx)(Y,{label:`Archived`,value:String(P.archived),tone:P.archived>0?`warn`:`neutral`})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`div`,{className:`toolbar`,children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`Search set ID, short name or title`})]}),(0,W.jsxs)(`label`,{className:`gift-page-size`,children:[(0,W.jsx)(`span`,{children:`Per page`}),(0,W.jsxs)(`select`,{value:String(l),onChange:e=>u(e.target.value===`all`?`all`:Number(e.target.value)),children:[(0,W.jsx)(`option`,{value:`10`,children:`10`}),(0,W.jsx)(`option`,{value:`20`,children:`20`}),(0,W.jsx)(`option`,{value:`50`,children:`50`}),(0,W.jsx)(`option`,{value:`100`,children:`100`}),(0,W.jsx)(`option`,{value:`all`,children:`All`})]})]}),(0,W.jsx)(`span`,{className:`gift-list-summary`,children:`Showing ${E.length} of ${t.length}`})]})}),(0,W.jsx)(`div`,{className:`table-wrap gift-table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Logo`}),(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Short name`}),(0,W.jsx)(`th`,{children:`Title`}),(0,W.jsx)(`th`,{children:`Documents`}),(0,W.jsx)(`th`,{children:`Official`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Sort order`}),(0,W.jsx)(`th`,{children:`Actions`})]})}),(0,W.jsxs)(`tbody`,{children:[j.map(e=>(0,W.jsxs)(`tr`,{className:e.Archived?`gift-row-disabled`:``,children:[(0,W.jsx)(`td`,{children:e.CoverDocumentID?(0,W.jsx)(Cr,{documentID:e.CoverDocumentID,className:`list-thumb`,showError:!1}):(0,W.jsx)(`div`,{className:`sticker-list-thumb-empty`,children:(0,W.jsx)(Ie,{size:14})})}),(0,W.jsx)(`td`,{className:`mono`,children:e.ID}),(0,W.jsx)(`td`,{className:`mono`,children:e.ShortName||(0,W.jsx)(`span`,{className:`muted-cell`,children:`None`})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`sort-order-editor`,children:[(0,W.jsx)(`input`,{className:`small-input title-input`,value:h[e.ID]??e.Title,onChange:t=>_(n=>({...n,[e.ID]:t.target.value}))}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:`Save`,path:`/api/actions/rename-sticker-set`,payload:()=>({set_id:e.ID,title:(h[e.ID]??e.Title).trim()}),onDone:()=>void T()})]})}),(0,W.jsx)(`td`,{children:e.Count}),(0,W.jsx)(`td`,{children:e.Official?(0,W.jsx)(J,{tone:`good`,children:`Yes`}):(0,W.jsx)(J,{children:`No`})}),(0,W.jsx)(`td`,{children:e.Archived?(0,W.jsx)(J,{tone:`danger`,children:`Archived`}):(0,W.jsx)(J,{tone:`good`,children:`Enabled`})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`sort-order-editor`,children:[(0,W.jsx)(`input`,{type:`number`,className:`small-input`,value:p[e.ID]??String(e.SortOrder),onChange:t=>m(n=>({...n,[e.ID]:t.target.value}))}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:`Save`,path:`/api/actions/set-sticker-set-sort-order`,payload:()=>({set_id:e.ID,sort_order:Number(p[e.ID]??e.SortOrder)}),onDone:()=>void T()})]})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`gift-table-actions`,children:[(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>y(e),children:[(0,W.jsx)(Oe,{size:13}),` `,`View`]}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:e.Archived?`Unarchive`:`Archive`,path:`/api/actions/set-sticker-set-archived`,payload:()=>({set_id:e.ID,archived:!e.Archived}),onDone:()=>void T()}),(0,W.jsx)(Z,{compact:!0,tone:`danger`,label:`Delete`,path:`/api/actions/delete-sticker-set`,payload:()=>({set_id:e.ID}),onDone:()=>void T()})]})})]},e.ID)),j.length===0&&(0,W.jsx)(Bt,{colSpan:9})]})]})}),l!==`all`&&E.length>0&&(0,W.jsxs)(`div`,{className:`gift-pager`,children:[(0,W.jsx)(`span`,{className:`gift-pager-range`,children:`Showing ${M}-${N} of ${E.length}`}),(0,W.jsxs)(`div`,{className:`gift-pager-controls`,children:[(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>f(e=>Math.max(1,e-1)),disabled:A<=1,children:[(0,W.jsx)(xe,{size:14}),` `,`Previous`]}),(0,W.jsx)(`span`,{className:`gift-pager-page`,children:`Page ${A} of ${D}`}),(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>f(e=>Math.min(D,e+1)),disabled:A>=D,children:[`Next`,` `,(0,W.jsx)(Se,{size:14})]})]})]}),v&&(0,W.jsx)(Er,{set:v,onClose:()=>y(null)}),b&&(0,W.jsx)(wr,{kind:e,onClose:()=>x(!1),onCreated:()=>void T()})]})}var kr=[`Love`,`Approval`,`Disapproval`,`Cheers`,`Laughter`,`Astonishment`,`Sadness`,`Anger`,`Neutral`,`Doubt`,`Silly`];function Ar({documentID:e}){let[t,n]=(0,g.useState)(!1);return t?(0,W.jsx)(`div`,{className:`sticker-list-thumb-empty`,children:(0,W.jsx)(Ie,{size:14})}):(0,W.jsx)(`video`,{className:`gif-catalog-thumb`,src:k.gifCatalogDocumentPreviewURL(e),muted:!0,loop:!0,autoPlay:!0,playsInline:!0,onError:()=>n(!0)})}function jr(){let[e,t]=(0,g.useState)([]),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(!1),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(10),[u,d]=(0,g.useState)(1),[f,p]=(0,g.useState)({}),[m,h]=(0,g.useState)({}),[_,v]=(0,g.useState)(!1);async function y(){a(!0),s(``);try{t((await k.gifCatalog()).rows??[])}catch(e){s(O(e))}finally{a(!1)}}(0,g.useEffect)(()=>{y()},[]);let b=(0,g.useMemo)(()=>{let t=n.trim().toLowerCase();return t?e.filter(e=>e.ID.includes(t)||e.Title.toLowerCase().includes(t)):e},[e,n]);(0,g.useEffect)(()=>{d(1)},[n,c]);let x=c===`all`?1:Math.max(1,Math.ceil(b.length/c)),S=Math.min(u,x),C=(0,g.useMemo)(()=>{if(c===`all`)return b;let e=(S-1)*c;return b.slice(e,e+c)},[b,S,c]),w=C.length===0?0:c===`all`?1:(S-1)*c+1,T=w===0?0:w+C.length-1,E=(0,g.useMemo)(()=>({total:e.length,enabled:e.filter(e=>e.Enabled).length,uncategorized:e.filter(e=>!e.Category).length}),[e]);return(0,W.jsxs)(G,{title:`GIFs`,eyebrow:`Curated GIFs served by @gif in the client's GIF picker (trending + search)`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn`,type:`button`,onClick:()=>y(),disabled:i,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]}),(0,W.jsx)(Z,{tone:`neutral`,label:`Auto-categorize`,path:`/api/actions/auto-categorize-gif-catalog`,payload:()=>({}),onDone:()=>void y()}),(0,W.jsx)(Z,{tone:`danger`,label:`Delete uncategorized`,path:`/api/actions/delete-uncategorized-gifs`,payload:()=>({}),onDone:()=>void y()}),(0,W.jsxs)(`button`,{className:`btn primary`,type:`button`,onClick:()=>v(!0),children:[(0,W.jsx)(Ye,{size:15}),` `,`Add GIF`]})]}),children:[o&&(0,W.jsx)(q,{children:o}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Total GIFs`,value:String(E.total)}),(0,W.jsx)(Y,{label:`Enabled`,value:String(E.enabled),tone:`good`}),(0,W.jsx)(Y,{label:`Uncategorized`,value:String(E.uncategorized),tone:E.uncategorized>0?`warn`:void 0})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`div`,{className:`toolbar`,children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:n,onChange:e=>r(e.target.value),placeholder:`Search ID or title`})]}),(0,W.jsxs)(`label`,{className:`gift-page-size`,children:[(0,W.jsx)(`span`,{children:`Per page`}),(0,W.jsxs)(`select`,{value:String(c),onChange:e=>l(e.target.value===`all`?`all`:Number(e.target.value)),children:[(0,W.jsx)(`option`,{value:`10`,children:`10`}),(0,W.jsx)(`option`,{value:`20`,children:`20`}),(0,W.jsx)(`option`,{value:`50`,children:`50`}),(0,W.jsx)(`option`,{value:`100`,children:`100`}),(0,W.jsx)(`option`,{value:`all`,children:`All`})]})]}),(0,W.jsx)(`span`,{className:`gift-list-summary`,children:`Showing ${b.length} of ${e.length}`})]})}),(0,W.jsx)(`div`,{className:`table-wrap gift-table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Preview`}),(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Title`}),(0,W.jsx)(`th`,{children:`Document ID`}),(0,W.jsx)(`th`,{children:`Added by`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Category`}),(0,W.jsx)(`th`,{children:`Sort order`}),(0,W.jsx)(`th`,{children:`Actions`})]})}),(0,W.jsxs)(`tbody`,{children:[C.map(e=>(0,W.jsxs)(`tr`,{className:e.Enabled?``:`gift-row-disabled`,children:[(0,W.jsx)(`td`,{children:(0,W.jsx)(Ar,{documentID:e.DocumentID})}),(0,W.jsx)(`td`,{className:`mono`,children:e.ID}),(0,W.jsx)(`td`,{children:e.Title||(0,W.jsx)(`span`,{className:`muted-cell`,children:`Untitled`})}),(0,W.jsx)(`td`,{className:`mono`,children:e.DocumentID}),(0,W.jsx)(`td`,{children:e.CreatedBy||(0,W.jsx)(`span`,{className:`muted-cell`,children:`—`})}),(0,W.jsx)(`td`,{children:e.Enabled?(0,W.jsx)(J,{tone:`good`,children:`Enabled`}):(0,W.jsx)(J,{tone:`danger`,children:`Disabled`})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`sort-order-editor`,children:[(0,W.jsxs)(`select`,{className:`small-input`,value:m[e.ID]??e.Category,onChange:t=>h(n=>({...n,[e.ID]:t.target.value})),children:[(0,W.jsx)(`option`,{value:``,children:`Uncategorized`}),kr.map(e=>(0,W.jsx)(`option`,{value:e,children:e},e))]}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:`Save`,path:`/api/actions/set-gif-catalog-category`,payload:()=>({id:e.ID,category:m[e.ID]??e.Category}),onDone:()=>void y()})]})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`sort-order-editor`,children:[(0,W.jsx)(`input`,{type:`number`,className:`small-input`,value:f[e.ID]??String(e.SortOrder),onChange:t=>p(n=>({...n,[e.ID]:t.target.value}))}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:`Save`,path:`/api/actions/set-gif-catalog-sort-order`,payload:()=>({id:e.ID,sort_order:Number(f[e.ID]??e.SortOrder)}),onDone:()=>void y()})]})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`gift-table-actions`,children:[(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:e.Enabled?`Disable`:`Enable`,path:`/api/actions/set-gif-catalog-enabled`,payload:()=>({id:e.ID,enabled:!e.Enabled}),onDone:()=>void y()}),(0,W.jsx)(Z,{compact:!0,tone:`danger`,label:`Delete`,path:`/api/actions/delete-gif-catalog-entry`,payload:()=>({id:e.ID}),onDone:()=>void y()})]})})]},e.ID)),C.length===0&&(0,W.jsx)(Bt,{colSpan:9})]})]})}),c!==`all`&&b.length>0&&(0,W.jsxs)(`div`,{className:`gift-pager`,children:[(0,W.jsx)(`span`,{className:`gift-pager-range`,children:`Showing ${w}-${T} of ${b.length}`}),(0,W.jsxs)(`div`,{className:`gift-pager-controls`,children:[(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>d(e=>Math.max(1,e-1)),disabled:S<=1,children:[(0,W.jsx)(xe,{size:14}),` `,`Previous`]}),(0,W.jsx)(`span`,{className:`gift-pager-page`,children:`Page ${S} of ${x}`}),(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>d(e=>Math.min(x,e+1)),disabled:S>=x,children:[`Next`,` `,(0,W.jsx)(Se,{size:14})]})]})]}),_&&(0,W.jsx)(Mr,{onClose:()=>v(!1),onCreated:()=>void y()})]})}function Mr({onClose:e,onCreated:t}){let[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(null),[o,s]=(0,g.useState)(null),[c,l]=(0,g.useState)(``),[u,d]=(0,g.useState)(!1),[f,p]=(0,g.useState)(``);function m(e){a(e),s(t=>(t&&URL.revokeObjectURL(t),e?URL.createObjectURL(e):null))}async function h(){if(!n.trim()||!i){p(`Title and a GIF/MP4 file are required.`);return}if(!c.trim()){p(`Please enter an operation reason`);return}d(!0),p(``);try{let r=new FormData;r.set(`metadata`,JSON.stringify({command_id:``,reason:c.trim(),confirm:!0,title:n.trim()})),r.set(`file`,i,i.name),await k.createGifCatalogEntry(r),t(),e()}catch(e){p(O(e))}finally{d(!1)}}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Add a GIF`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`New catalog entry`}),(0,W.jsx)(`h2`,{children:`Add a GIF`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:e,disabled:u,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsx)(`div`,{className:`gift-fields-grid`,children:(0,W.jsxs)(`label`,{children:[(0,W.jsx)(`span`,{children:`Title`}),(0,W.jsx)(`input`,{value:n,maxLength:128,onChange:e=>r(e.target.value)})]})}),(0,W.jsxs)(`label`,{className:`gift-file-picker ${i?`has-file`:``}`,children:[(0,W.jsx)(`input`,{type:`file`,accept:`.gif,.mp4,image/gif,video/mp4`,onChange:e=>m(e.target.files?.[0]??null)}),(0,W.jsxs)(`span`,{className:`gift-file-copy`,children:[(0,W.jsx)(`span`,{className:`gift-field-label`,children:`File`}),(0,W.jsx)(`strong`,{children:i?i.name:`Choose a GIF or MP4 file`})]}),(0,W.jsx)(`span`,{className:`gift-file-action`,children:i?`Change file`:`Choose file`})]}),o&&(0,W.jsx)(`div`,{className:`gif-catalog-preview`,children:i?.type===`video/mp4`?(0,W.jsx)(`video`,{src:o,autoPlay:!0,loop:!0,muted:!0,playsInline:!0}):(0,W.jsx)(`img`,{src:o,alt:``})}),(0,W.jsxs)(`label`,{className:`gift-reason-field`,children:[(0,W.jsx)(`span`,{children:`Audit reason`}),(0,W.jsx)(`input`,{value:c,placeholder:`Briefly describe why this GIF is being added`,onChange:e=>l(e.target.value)})]}),f&&(0,W.jsx)(q,{children:f})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:e,disabled:u,children:`Close`}),(0,W.jsxs)(`button`,{className:`btn primary`,type:`button`,onClick:h,disabled:u,children:[u?(0,W.jsx)(I,{className:`spin`,size:15}):(0,W.jsx)(ht,{size:15}),`Add GIF`]})]})]})}),document.body)}function Nr(){let[e,t]=(0,g.useState)(`settings`);return(0,W.jsxs)(G,{title:`Server Settings`,eyebrow:`Identity, .env, and live process/service control`,children:[(0,W.jsxs)(`div`,{className:`tab-bar`,role:`tablist`,"aria-label":`Server Settings sections`,children:[(0,W.jsx)(`button`,{className:`tab-btn ${e===`settings`?`active`:``}`,type:`button`,role:`tab`,"aria-selected":e===`settings`,onClick:()=>t(`settings`),children:`Settings`}),(0,W.jsx)(`button`,{className:`tab-btn ${e===`services`?`active`:``}`,type:`button`,role:`tab`,"aria-selected":e===`services`,onClick:()=>t(`services`),children:`Services`})]}),e===`settings`?(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsx)(Pr,{}),(0,W.jsx)(Fr,{}),(0,W.jsx)(Lr,{})]}):(0,W.jsx)(`div`,{className:`stacked-sections`,children:(0,W.jsx)(Jr,{})})]})}function Pr(){let[e,t]=(0,g.useState)(null),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(!1),[c,l]=(0,g.useState)(0),[u,d]=(0,g.useState)(!1),[f,p]=(0,g.useState)(``);async function m(){p(``);try{let e=await k.serverIdentity();t(e),r(e.name),a(e.description),d(!1)}catch(e){p(O(e))}}return(0,g.useEffect)(()=>{m()},[]),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Server identity`}),f&&(0,W.jsx)(q,{children:f}),e?(0,W.jsxs)(`div`,{className:`card-body identity-card`,children:[(0,W.jsxs)(`div`,{className:`identity-layout`,children:[(0,W.jsxs)(`div`,{className:`avatar-edit-slot`,children:[e.icon_ext&&!u?(0,W.jsx)(`img`,{className:`avatar-photo-img`,src:k.serverIconURL()+`&b=${c}`,alt:``,style:{width:88,height:88},onError:()=>d(!0)}):(0,W.jsx)(`div`,{className:`avatar-fallback server-icon-fallback`,style:{width:88,height:88},children:(0,W.jsx)(Ie,{size:26})}),(0,W.jsx)(`button`,{className:`icon-btn avatar-edit-btn`,type:`button`,"aria-label":`Change server icon`,title:`Change server icon`,onClick:()=>s(!0),children:(0,W.jsx)(Le,{size:14})})]}),(0,W.jsxs)(`div`,{className:`server-identity-fields`,children:[(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsx)(`span`,{children:`Name`}),(0,W.jsx)(`input`,{value:n,maxLength:128,onChange:e=>r(e.target.value)})]}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsx)(`span`,{children:`Description`}),(0,W.jsx)(`textarea`,{rows:4,value:i,maxLength:512,onChange:e=>a(e.target.value)})]})]})]}),(0,W.jsx)(`div`,{className:`gift-table-actions identity-save-row`,children:(0,W.jsx)(Z,{tone:`neutral`,label:`Save identity`,path:`/api/actions/set-server-identity`,payload:()=>({name:n,description:i}),onDone:()=>void m()})})]}):(0,W.jsx)(Vt,{label:`Loading identity...`}),o&&(0,W.jsx)(Ir,{hasIcon:!!e?.icon_ext,onClose:()=>s(!1),onDone:()=>{l(e=>e+1),d(!1),m()}})]})}function Fr(){let[e,t]=(0,g.useState)(null),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(``);async function u(){l(``);try{let e=await k.serverIdentity();t(e),r(e.welcome_message_phone_template??``),a(e.welcome_message_email_template??``),s(e.login_code_message_template??``)}catch(e){l(O(e))}}(0,g.useEffect)(()=>{u()},[]);let d=n.trim()!==``,f=i.trim()!==``,p=o.trim()!==``,m=(o.match(/\{\{code\}\}/g)??[]).length,h=p&&m!==1;return(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Login notifications`}),c&&(0,W.jsx)(q,{children:c}),e?(0,W.jsxs)(`div`,{className:`card-body`,children:[(0,W.jsxs)(`p`,{style:{color:`var(--muted)`,marginTop:0},children:[`Sent from the official system account on every completed sign-in. Use `,(0,W.jsx)(`code`,{children:`{{server_name}}`}),` to insert the server's configured name.`]}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsxs)(`span`,{children:[`Phone sign-in template`,` `,d?(0,W.jsx)(`span`,{className:`badge good`,children:`custom`}):(0,W.jsx)(`span`,{className:`badge`,children:`default`})]}),(0,W.jsx)(`textarea`,{rows:4,value:n,onChange:e=>r(e.target.value),placeholder:e.default_welcome_message_phone_template})]}),(0,W.jsx)(`div`,{className:`gift-table-actions`,children:(0,W.jsx)(Z,{tone:`neutral`,compact:!0,label:`Reset to default`,path:`/api/actions/set-welcome-message-templates`,payload:()=>({phone_template:``,email_template:i}),disabled:!d,onDone:()=>{r(``),u()}})}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsxs)(`span`,{children:[`Email sign-in template`,` `,f?(0,W.jsx)(`span`,{className:`badge good`,children:`custom`}):(0,W.jsx)(`span`,{className:`badge`,children:`default`})]}),(0,W.jsx)(`textarea`,{rows:4,value:i,onChange:e=>a(e.target.value),placeholder:e.default_welcome_message_email_template})]}),(0,W.jsx)(`div`,{className:`gift-table-actions`,children:(0,W.jsx)(Z,{tone:`neutral`,compact:!0,label:`Reset to default`,path:`/api/actions/set-welcome-message-templates`,payload:()=>({phone_template:n,email_template:``}),disabled:!f,onDone:()=>{a(``),u()}})}),(0,W.jsx)(`div`,{className:`gift-table-actions identity-save-row`,children:(0,W.jsx)(Z,{tone:`neutral`,label:`Save login notification templates`,path:`/api/actions/set-welcome-message-templates`,payload:()=>({phone_template:n,email_template:i}),onDone:()=>void u()})}),(0,W.jsxs)(`p`,{style:{color:`var(--muted)`,marginTop:`1.5em`,borderTop:`1px solid var(--line)`,paddingTop:`1em`},children:[`Sent from the official system account with every login code (SMS and email alike). Must contain `,(0,W.jsx)(`code`,{children:`{{code}}`}),` exactly once -- that's where the actual code is inserted and bolded. `,(0,W.jsx)(`code`,{children:`{{server_name}}`}),` is optional and may appear any number of times.`]}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsxs)(`span`,{children:[`Login-code message template`,` `,p?(0,W.jsx)(`span`,{className:`badge good`,children:`custom`}):(0,W.jsx)(`span`,{className:`badge`,children:`default`})]}),(0,W.jsx)(`textarea`,{rows:5,value:o,onChange:e=>s(e.target.value),placeholder:e.default_login_code_message_template}),h&&(0,W.jsx)(`span`,{style:{color:`var(--danger-text)`,fontSize:`0.85em`},children:m===0?`Must contain {{code}} exactly once -- it is currently missing.`:`Must contain {{code}} exactly once -- it currently appears ${m} times.`})]}),(0,W.jsx)(`div`,{className:`gift-table-actions`,children:(0,W.jsx)(Z,{tone:`neutral`,compact:!0,label:`Reset to default`,path:`/api/actions/set-login-code-message-template`,payload:()=>({template:``}),disabled:!p,onDone:()=>{s(``),u()}})}),(0,W.jsx)(`div`,{className:`gift-table-actions identity-save-row`,children:(0,W.jsx)(Z,{tone:`neutral`,label:`Save login-code message template`,path:`/api/actions/set-login-code-message-template`,payload:()=>({template:o}),disabled:h,onDone:()=>void u()})})]}):(0,W.jsx)(Vt,{label:`Loading login notification templates...`})]})}function Ir({hasIcon:e,onClose:t,onDone:n}){let[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(!1),[d,f]=(0,g.useState)(``);(0,g.useEffect)(()=>{if(!r){o(``);return}let e=URL.createObjectURL(r);return o(e),()=>URL.revokeObjectURL(e)},[r]);async function p(){if(!r){f(`Choose an image file first.`);return}if(!s.trim()){f(`Please enter an operation reason`);return}u(!0),f(``);try{let e=new FormData;e.set(`metadata`,JSON.stringify({command_id:``,reason:s.trim(),confirm:!0})),e.set(`file`,r,r.name);let i=await k.uploadServerIcon(e);if(i.error){f(i.error);return}n(),t()}catch(e){f(O(e))}finally{u(!1)}}async function m(){if(!s.trim()){f(`Please enter an operation reason`);return}u(!0),f(``);try{let e=await k.action(`/api/actions/remove-server-icon`,{command_id:``,reason:s.trim(),confirm:!0});if(e.error){f(e.error);return}n(),t()}catch(e){f(O(e))}finally{u(!1)}}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Change server icon`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Server identity`}),(0,W.jsx)(`h2`,{children:`Change server icon`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:t,disabled:l,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsxs)(`label`,{className:`gift-file-picker ${r?`has-file`:``}`,children:[(0,W.jsx)(`input`,{type:`file`,accept:`.png,.jpg,.jpeg,.webp,.gif,image/png,image/jpeg,image/webp,image/gif`,onChange:e=>i(e.target.files?.[0]??null)}),a?(0,W.jsx)(`img`,{className:`gift-file-icon`,src:a,alt:``,style:{objectFit:`cover`}}):(0,W.jsx)(Le,{size:22}),(0,W.jsxs)(`span`,{className:`gift-file-copy`,children:[(0,W.jsx)(`span`,{className:`gift-field-label`,children:`New icon`}),(0,W.jsx)(`strong`,{children:r?r.name:`Choose a PNG, JPEG, WebP, or GIF image`})]}),(0,W.jsx)(`span`,{className:`gift-file-action`,children:r?`Change file`:`Choose file`})]}),(0,W.jsxs)(`label`,{className:`gift-reason-field`,children:[(0,W.jsx)(`span`,{children:`Audit reason`}),(0,W.jsx)(`input`,{value:s,placeholder:`Briefly describe why the server icon is changing`,onChange:e=>c(e.target.value)})]}),d&&(0,W.jsx)(q,{children:d})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:t,disabled:l,children:`Close`}),e&&(0,W.jsxs)(`button`,{className:`btn danger icon-text`,type:`button`,onClick:()=>void m(),disabled:l,children:[l?(0,W.jsx)(I,{className:`spin`,size:15}):(0,W.jsx)(pt,{size:15}),`Remove icon`]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`button`,onClick:()=>void p(),disabled:l,children:[l?(0,W.jsx)(I,{className:`spin`,size:15}):(0,W.jsx)(ht,{size:15}),`Upload icon`]})]})]})}),document.body)}function Lr(){let[e,t]=(0,g.useState)([]),[n,r]=(0,g.useState)({}),[i,a]=(0,g.useState)({}),[o,s]=(0,g.useState)(``);async function c(){s(``);try{let e=await k.serverEnv();t(e);let n={};for(let t of e)for(let e of t.fields)n[e.key]=e.value;r(n)}catch(e){s(O(e))}}return(0,g.useEffect)(()=>{c()},[]),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Environment (.env)`,text:`${(0,g.useMemo)(()=>e.reduce((e,t)=>e+t.fields.length,0),[e])} setting(s) across ${e.length} group(s). Changes take effect on the next Restart/Update.`}),o&&(0,W.jsx)(q,{children:o}),(0,W.jsx)(`div`,{className:`env-groups`,children:e.map(e=>{let t=!!i[e.title];return(0,W.jsxs)(`div`,{className:`env-group ${t?`open`:``}`,children:[(0,W.jsxs)(`button`,{className:`env-group-toggle`,type:`button`,"aria-expanded":t,onClick:()=>a(t=>({...t,[e.title]:!t[e.title]})),children:[(0,W.jsxs)(`span`,{className:`env-group-toggle-text`,children:[(0,W.jsx)(`span`,{className:`env-group-toggle-title`,children:e.title}),(0,W.jsx)(`span`,{className:`env-group-toggle-count`,children:`${e.fields.length} field${e.fields.length===1?``:`s`}`})]}),(0,W.jsx)(be,{size:16,className:`env-group-chevron`})]}),t&&(0,W.jsxs)(`div`,{className:`env-group-body`,children:[e.description&&(0,W.jsx)(`p`,{className:`env-group-desc`,children:e.description}),e.fields.map(e=>(0,W.jsxs)(`label`,{className:`form-field env-field`,children:[(0,W.jsx)(`span`,{className:`mono`,children:e.key}),e.description&&(0,W.jsx)(`span`,{className:`env-field-desc`,children:e.description}),(0,W.jsx)(`input`,{type:e.sensitive?`password`:`text`,value:n[e.key]??``,placeholder:e.default_value,onChange:t=>r(n=>({...n,[e.key]:t.target.value}))})]},e.key))]})]},e.title)})}),(0,W.jsx)(`div`,{className:`gift-table-actions env-save-row`,children:(0,W.jsx)(Z,{tone:`warn`,label:`Save .env changes`,path:`/api/actions/update-server-env`,payload:()=>({values:n}),onDone:()=>void c()})})]})}function Rr(e){return new Promise(t=>setTimeout(t,e))}function zr(){let[e,t]=(0,g.useState)(!1),[n,r]=(0,g.useState)(!1),i=(0,g.useRef)(!1);return{waiting:e,timedOut:n,watch:(0,g.useCallback)(async(e=15e4)=>{i.current=!1,r(!1),t(!0);let n=``;try{n=(await k.session()).boot_id??``}catch{}let a=Date.now()+e;for(;Date.now(){i.current=!0,t(!1),r(!1)},[])}}function Br({label:e,timedOut:t,onDismiss:n}){return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsx)(`section`,{className:`modal command-modal restart-overlay`,role:`dialog`,"aria-modal":`true`,"aria-label":e,children:t?(0,W.jsxs)(`div`,{className:`command-body restart-overlay-body`,children:[(0,W.jsx)(q,{children:`The admin panel did not come back within the expected time. It may still be building/restarting -- reload manually in a bit, or check the server logs.`}),(0,W.jsxs)(`div`,{className:`gift-table-actions restart-overlay-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:n,children:`Dismiss`}),(0,W.jsx)(`button`,{className:`btn primary`,type:`button`,onClick:()=>window.location.reload(),children:`Reload now`})]})]}):(0,W.jsxs)(`div`,{className:`command-body restart-overlay-body`,children:[(0,W.jsx)(I,{className:`spin`,size:28}),(0,W.jsx)(`p`,{children:e})]})})}),document.body)}function Vr(e){switch(e){case`good`:return(0,W.jsx)(F,{size:15});case`warn`:return(0,W.jsx)(I,{className:`spin`,size:15});case`danger`:return(0,W.jsx)(ne,{size:15});default:return(0,W.jsx)(Ce,{size:15})}}function Hr({icon:e,name:t,tone:n,statusLabel:r,detail:i}){return(0,W.jsxs)(`div`,{className:`service-card tone-${n}`,children:[(0,W.jsx)(`div`,{className:`service-card-icon`,children:e}),(0,W.jsxs)(`div`,{className:`service-card-body`,children:[(0,W.jsx)(`div`,{className:`service-card-name`,children:t}),(0,W.jsx)(`div`,{className:`service-card-detail`,children:i??`\xA0`})]}),(0,W.jsxs)(`div`,{className:`service-card-status`,children:[Vr(n),(0,W.jsx)(`span`,{children:r})]})]})}var Ur={postgres:(0,W.jsx)(Te,{size:18}),redis:(0,W.jsx)(re,{size:18}),minio:(0,W.jsx)(Pe,{size:18})};function Wr(e){let t=e.state.toLowerCase(),n=e.health.toLowerCase();return t!==`running`||n===`unhealthy`?`danger`:n===`starting`?`warn`:`good`}function Gr(e){return e.state.toLowerCase()===`running`?e.health?e.health:`running`:e.state||`stopped`}var Kr=4e3;function qr({onUpdateStarted:e}){let[t,n]=(0,g.useState)(null),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(!1),[s,c]=(0,g.useState)(``),l=(0,g.useCallback)(async()=>{o(!0),c(``),i(``);try{let e=await k.checkServerUpdates();n(e.commits_behind),i(e.commits_behind>0?`${e.commits_behind} new commit${e.commits_behind===1?``:`s`} pulled from GitHub.`:`Already up to date.`)}catch(e){c(O(e))}finally{o(!1)}},[]);(0,g.useEffect)(()=>{l()},[l]);async function u(){let n=t??0;if(window.confirm(`Pull ${n} commit(s), rebuild, and restart owpengram-server and the admin panel?`)){o(!0),c(``);try{let t=await k.action(`/api/actions/update-server`,{command_id:``,reason:`Update via Services tab`,confirm:!0});if(t.error){c(t.error),o(!1);return}e(`Pulled ${n} commit${n===1?``:`s`} -- rebuilding and restarting owpengram-server and the admin panel...`)}catch(e){c(O(e)),o(!1)}}}let d=(t??0)>0;return(0,W.jsxs)(`button`,{className:`btn compact-btn icon-text ${d?`danger`:``}`,type:`button`,disabled:a,title:s||r||void 0,onClick:()=>void(d?u():l()),children:[a?(0,W.jsx)(I,{className:`spin`,size:15}):d?(0,W.jsx)(Ee,{size:15}):(0,W.jsx)(B,{size:15}),d?`Update (${t})`:`Check updates`]})}function Jr(){let[e,t]=(0,g.useState)(null),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(null),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(``),u=zr(),d=(0,g.useRef)(!1);d.current=u.waiting;let f=(0,g.useCallback)(async()=>{if(!d.current){try{t(await k.serverStatus()),r(``)}catch(e){r(O(e))}try{a(await k.dockerStatus()),s(``)}catch(e){s(O(e))}}},[]);return(0,g.useEffect)(()=>{f();let e=window.setInterval(()=>void f(),Kr);return()=>window.clearInterval(e)},[f]),(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Services`,action:(0,W.jsxs)(`div`,{className:`services-header-actions`,children:[(0,W.jsx)(qr,{onUpdateStarted:e=>{l(e),u.watch()}}),(0,W.jsx)(Z,{compact:!0,tone:`primary`,label:`Restart`,path:`/api/actions/restart-server`,payload:()=>({}),onDone:()=>{l(`Restarting owpengram-server and the admin panel...`),u.watch()}})]})}),n&&(0,W.jsx)(q,{children:n}),o&&(0,W.jsx)(q,{children:o}),e===null&&i===null&&!n&&!o?(0,W.jsx)(Vt,{label:`Loading service status...`}):(0,W.jsxs)(`div`,{className:`service-grid`,children:[e&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(Hr,{icon:(0,W.jsx)(tt,{size:18}),name:`Server`,tone:e.ServerAlive?`good`:`danger`,statusLabel:e.ServerAlive?`running`:`stopped`,detail:e.ServerAlive?`pid ${e.ServerPID}`:void 0}),(0,W.jsx)(Hr,{icon:(0,W.jsx)(ot,{size:18}),name:`admin panel`,tone:e.AdminAlive?`good`:`danger`,statusLabel:e.AdminAlive?`running`:`stopped`,detail:e.AdminAlive?`pid ${e.AdminPID}`:void 0})]}),i?.map(e=>(0,W.jsx)(Hr,{icon:Ur[e.name]??(0,W.jsx)(Te,{size:18}),name:e.name,tone:Wr(e),statusLabel:Gr(e),detail:e.state},e.name))]})]}),u.waiting&&(0,W.jsx)(Br,{label:c,timedOut:!1,onDismiss:u.dismiss}),u.timedOut&&(0,W.jsx)(Br,{label:c,timedOut:!0,onDismiss:u.dismiss})]})}var Yr=`open,in_review,action_pending,action_failed,appeal_review`,Xr=[{value:Yr,label:`Active queue`},{value:`open,in_review,action_pending,action_failed,resolved,dismissed,appeal_review`,label:`All statuses`},{value:`open`,label:`Open`},{value:`in_review`,label:`In review`},{value:`action_pending`,label:`Action pending`},{value:`action_failed`,label:`Action failed`},{value:`appeal_review`,label:`Appeal review`},{value:`resolved`,label:`Resolved`},{value:`dismissed`,label:`Dismissed`}];function Zr({navigate:e}){let[t,n]=(0,g.useState)(Yr),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)([]),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(``);async function d(){c(!0),u(``);try{let e=new URLSearchParams({statuses:t,limit:`100`});r.trim()&&e.set(`assigned_to`,r.trim()),o((await k.moderationCases(e)).cases)}catch(e){u(O(e))}finally{c(!1)}}(0,g.useEffect)(()=>{d()},[]);let f=a.filter(e=>e.Status===`action_pending`||e.Status===`action_failed`).length,p=a.filter(e=>e.Severity===4).length;return(0,W.jsxs)(G,{title:`Reports and Moderation`,eyebrow:`Moderation / Cases`,actions:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:d,disabled:s,children:[(0,W.jsx)(B,{size:15,className:s?`spin`:``}),` `,`Refresh`]}),children:[l&&(0,W.jsx)(q,{children:l}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Current queue`,value:String(a.length)}),(0,W.jsx)(Y,{label:`Critical cases`,value:String(p),tone:p?`danger`:`neutral`}),(0,W.jsx)(Y,{label:`Pending / failed actions`,value:String(f),tone:f?`warn`:`good`})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),d()},children:[(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Status`}),(0,W.jsx)(`select`,{"aria-label":`Case status filter`,value:t,onChange:e=>n(e.target.value),children:Xr.map(e=>(0,W.jsx)(`option`,{value:e.value,children:e.label},e.value))})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Reviewer`}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`Leave blank for all`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:s,children:[(0,W.jsx)(at,{size:15}),` `,`Search`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Case`}),(0,W.jsx)(`th`,{children:`Target`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Severity`}),(0,W.jsx)(`th`,{children:`Reports / Reporters`}),(0,W.jsx)(`th`,{children:`Reviewer`}),(0,W.jsx)(`th`,{children:`Latest report`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[a.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsxs)(`td`,{className:`mono`,children:[`#`,t.ID]}),(0,W.jsx)(`td`,{className:`mono`,children:ri(t.Target.Type,t.Target.ID)}),(0,W.jsx)(`td`,{children:(0,W.jsx)(Qr,{status:t.Status})}),(0,W.jsx)(`td`,{children:(0,W.jsx)(ei,{value:t.Severity})}),(0,W.jsxs)(`td`,{children:[t.ReportCount,` / `,t.DistinctReporterCount]}),(0,W.jsx)(`td`,{children:t.AssignedTo||`-`}),(0,W.jsx)(`td`,{children:U(t.LastReportAt)}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,onClick:()=>e(`/moderation/${t.ID}`),children:[`Review`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),a.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})})]})}function Qr({status:e}){return(0,W.jsx)(J,{tone:e===`resolved`||e===`dismissed`?`good`:e===`action_failed`?`danger`:e===`action_pending`?`warn`:`neutral`,children:ni(`status`,e)})}var $r={low:`Low`,medium:`Medium`,high:`High`,critical:`Critical`};function ei({value:e}){let t=[``,`low`,`medium`,`high`,`critical`][e];return(0,W.jsx)(J,{tone:e>=4?`danger`:e>=3?`warn`:`neutral`,children:t?$r[t]:e})}var ti={status:{open:`Open`,in_review:`In review`,action_pending:`Action pending`,action_failed:`Action failed`,appeal_review:`Appeal review`,resolved:`Resolved`,dismissed:`Dismissed`},targetType:{channel:`Channel`,chat:`Group`,user:`Account`},source:{account_peer:`Account / peer`,antispam_false_positive:`Anti-spam false positive`,channel_spam:`Channel spam`,encrypted_spam:`Encrypted-chat spam`,ephemeral:`Ephemeral media`,messages:`Messages`,messages_spam:`Message spam`,profile_photo:`Profile photo`,reaction:`Reaction`,sponsored:`Sponsored message`,story:`Story`},reason:{child_abuse:`Child abuse`,copyright:`Copyright`,fake:`Fake`,geo_irrelevant:`Location-irrelevant`,illegal_drugs:`Illegal drugs`,other:`Other`,personal_details:`Personal details`,pornography:`Pornography`,spam:`Spam`,violence:`Violence`}};function ni(e,t){return ti[e]?.[t]??t}function ri(e,t){return`${ni(`targetType`,e)} #${t}`}function ii({id:e,navigate:t}){let[n,r]=(0,g.useState)(null),[i,a]=(0,g.useState)(null),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(`no_violation`),[u,d]=(0,g.useState)(``),[f,p]=(0,g.useState)(``),[m,h]=(0,g.useState)(!0),[_,v]=(0,g.useState)(!1),[y,b]=(0,g.useState)(``);function x(e){a(e),e&&(d(e.Items.filter(e=>e.Kind===`message`).map(e=>Number(e.ItemID)).filter(e=>Number.isSafeInteger(e)&&e>0).join(`, `)),p(String(e.ReporterUserID)))}async function S(){b(``);try{let t=await k.moderationCase(e);r(t);let n=t.ReportIDs[0];x(n?await k.moderationReport(n):null)}catch(e){b(O(e))}}(0,g.useEffect)(()=>{S()},[e]);let C=(0,g.useMemo)(()=>ai(c,n?.Case.Target.Type,oi(u),Number(f),m),[c,n?.Case.Target.Type,u,f,m]),w=(0,g.useMemo)(()=>n?si(n):{actions:[],label:`None`,blocked:!1},[n]);async function T(){if(n){v(!0),b(``);try{await k.claimModerationCase(e,n.Case.Version),await S()}catch(e){b(O(e))}finally{v(!1)}}}async function E(){if(!n||!o.trim()){b(`A review reason is required.`);return}if(c===`delete_messages`&&C.length===0){b(n.Case.Target.Type===`user`?`Private-message deletion requires valid evidence message IDs and the reporter's owner_user_id.`:`Channel-message deletion requires at least one valid evidence message ID.`);return}if(window.confirm(`Submit the “${ci(c)}” decision? The action will run through the durable action queue.`)){v(!0),b(``);try{r((await k.decideModerationCase(e,{expected_version:n.Case.Version,reason:o.trim(),kind:c===`no_violation`?`no_violation`:`violation`,actions:C})).case),s(``)}catch(e){b(O(e))}finally{v(!1)}}}async function D(t,i){if(!n||!o.trim()){b(`An appeal review reason is required.`);return}if(window.confirm(i?`Grant this appeal?`:`Deny this appeal?`)){v(!0);try{r((await k.reviewModerationAppeal(e,t,{expected_version:n.Case.Version,reason:o.trim(),granted:i,actions:i?w.actions:[]})).case),s(``)}catch(e){b(O(e))}finally{v(!1)}}}if(y&&!n)return(0,W.jsx)(q,{children:y});if(!n)return(0,W.jsx)(Vt,{label:`Loading moderation case…`});let A=n.Case,j=A.Status===`open`||A.Status===`in_review`||A.Status===`appeal_review`,M=(A.Status===`in_review`||A.Status===`action_failed`)&&!!A.AssignedTo,N=M&&(A.Status!==`action_failed`||c!==`no_violation`),P=n.Appeals.find(e=>e.Status===`pending`);return(0,W.jsxs)(G,{title:`Review case #${A.ID}`,eyebrow:`Moderation / Case detail`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,onClick:()=>t(`/moderation`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to queue`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,onClick:S,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]})]}),children:[y&&(0,W.jsx)(q,{children:y}),(0,W.jsx)(Rt,{main:(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:ri(A.Target.Type,A.Target.ID)}),(0,W.jsx)(`div`,{className:`entity-subtitle`,children:`Version ${A.Version} · Updated ${U(A.UpdatedAt)}`})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[(0,W.jsx)(Qr,{status:A.Status}),(0,W.jsx)(ei,{value:A.Severity})]})]}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Target`,value:ri(A.Target.Type,A.Target.ID),mono:!0}),(0,W.jsx)(X,{label:`Reports`,value:`${A.ReportCount} reports from ${A.DistinctReporterCount} reporters`}),(0,W.jsx)(X,{label:`Reviewer`,value:A.AssignedTo||`-`}),(0,W.jsx)(X,{label:`First / latest report`,value:`${U(A.FirstReportAt)} / ${U(A.LastReportAt)}`})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Report evidence`,text:`Shows up to the latest 100 reports; snapshots are frozen when reports are admitted.`}),(0,W.jsx)(`div`,{className:`toolbar`,children:n.ReportIDs.map(e=>(0,W.jsxs)(`button`,{className:`btn`,onClick:async()=>x(await k.moderationReport(e)),children:[`#`,e]},e))}),i&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Source / Reason`,value:`${ni(`source`,i.Source)} / ${ni(`reason`,i.Reason)}`}),(0,W.jsx)(X,{label:`Reporter`,value:String(i.ReporterUserID),mono:!0}),(0,W.jsx)(X,{label:`Option`,value:i.Option,mono:!0}),(0,W.jsx)(X,{label:`Time`,value:U(i.CreatedAt)})]}),i.Comment&&(0,W.jsx)(`p`,{className:`about-text`,children:i.Comment}),(0,W.jsx)(Ht,{value:JSON.stringify(i,null,2)})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Decision and action audit`,text:`Actions run idempotently through a lease worker; failures retain their error and attempt count.`}),(0,W.jsx)(Ht,{value:JSON.stringify({decisions:n.Decisions,actions:n.Actions},null,2)})]}),n.Appeals.length>0&&(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Appeals`}),(0,W.jsx)(Ht,{value:JSON.stringify(n.Appeals,null,2)})]})]}),side:(0,W.jsxs)(`section`,{className:`action-dock`,children:[(0,W.jsx)(`div`,{className:`dock-title`,children:`Case actions`}),j&&(0,W.jsxs)(`button`,{className:`btn primary icon-text`,disabled:_,onClick:T,children:[(0,W.jsx)(ot,{size:15}),` `,A.AssignedTo?`Renew claim`:`Claim case`]}),(0,W.jsxs)(`label`,{className:`field`,children:[(0,W.jsx)(`span`,{children:`Review reason`}),(0,W.jsx)(`textarea`,{value:o,onChange:e=>s(e.target.value),rows:5})]}),(0,W.jsxs)(`label`,{className:`field`,children:[(0,W.jsx)(`span`,{children:`Decision template`}),(0,W.jsxs)(`select`,{value:c,onChange:e=>l(e.target.value),children:[(0,W.jsx)(`option`,{value:`no_violation`,children:`No violation (dismiss report)`}),(0,W.jsx)(`option`,{value:`scam`,children:`Mark as SCAM`}),(0,W.jsx)(`option`,{value:`fake`,children:`Mark as FAKE`}),(0,W.jsx)(`option`,{value:`freeze`,children:`Freeze account`}),(0,W.jsx)(`option`,{value:`scam_freeze`,children:`SCAM + freeze`}),(0,W.jsx)(`option`,{value:`fake_freeze`,children:`FAKE + freeze`}),(0,W.jsx)(`option`,{value:`delete_messages`,children:`Delete messages covered by evidence`}),(0,W.jsx)(`option`,{value:`delete_account`,children:`Delete account`})]})]}),c===`delete_messages`&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`label`,{className:`field`,children:[(0,W.jsx)(`span`,{children:`Evidence message IDs (comma-separated)`}),(0,W.jsx)(`input`,{value:u,onChange:e=>d(e.target.value),placeholder:`101, 102`})]}),A.Target.Type===`user`&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`label`,{className:`field`,children:[(0,W.jsx)(`span`,{children:`Private-chat owner_user_id`}),(0,W.jsx)(`input`,{value:f,onChange:e=>p(e.target.value),inputMode:`numeric`})]}),(0,W.jsxs)(`label`,{className:`field checkbox-field`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:m,onChange:e=>h(e.target.checked)}),(0,W.jsx)(`span`,{children:`Revoke for both sides`})]})]}),(0,W.jsx)(q,{children:`The server will verify again that every message ID exists in this case's immutable report evidence.`})]}),A.Status===`action_failed`&&c===`no_violation`&&(0,W.jsx)(q,{children:`The action was partially executed and cannot be changed directly to no violation. Select a new action to retry while retaining the previous failure audit.`}),M&&(0,W.jsxs)(`button`,{className:`btn danger icon-text`,disabled:_||!N,onClick:E,children:[(0,W.jsx)(F,{size:15}),` `,A.Status===`action_failed`?`Retry action`:`Submit decision`]}),P&&A.AssignedTo&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`div`,{className:`dock-title`,children:`Appeal review #${P.ID}`}),(0,W.jsx)(X,{label:`Automatic remedy after approval`,value:w.label}),w.blocked&&(0,W.jsx)(q,{children:`The case contains a completed irreversible deletion. It cannot be marked as approved and restored; deny it or escalate for manual handling.`}),(0,W.jsx)(`button`,{className:`btn`,disabled:_,onClick:()=>D(P.ID,!1),children:`Deny appeal`}),(0,W.jsx)(`button`,{className:`btn primary`,disabled:_||w.blocked,onClick:()=>D(P.ID,!0),children:`Grant appeal`})]})]})})]})}function ai(e,t,n,r,i){switch(e){case`scam`:return[{kind:`mark_scam`,payload:{}}];case`fake`:return[{kind:`mark_fake`,payload:{}}];case`freeze`:return[{kind:`freeze_account`,payload:{}}];case`scam_freeze`:return[{kind:`mark_scam`,payload:{}},{kind:`freeze_account`,payload:{}}];case`fake_freeze`:return[{kind:`mark_fake`,payload:{}},{kind:`freeze_account`,payload:{}}];case`delete_messages`:return n.length===0?[]:t===`channel`?[{kind:`delete_channel_message`,payload:{ids:n}}]:t===`user`&&Number.isSafeInteger(r)&&r>0?[{kind:`delete_private_message`,payload:{owner_user_id:r,ids:n,revoke:i}}]:[];case`delete_account`:return[{kind:`delete_account`,payload:{}}];default:return[]}}function oi(e){let t=e.split(/[,\s]+/).filter(Boolean).map(Number);return t.length===0||t.some(e=>!Number.isSafeInteger(e)||e<=0)?[]:[...new Set(t)]}function si(e){let t=!1,n=!1,r=!1;for(let i of[...e.Actions].sort((e,t)=>e.ID-t.ID))if(i.Status===`succeeded`)switch(i.Kind){case`mark_scam`:case`mark_fake`:t=!0;break;case`clear_peer_flags`:t=!1;break;case`freeze_account`:n=!0;break;case`unfreeze_account`:n=!1;break;case`delete_private_message`:case`delete_channel_message`:case`delete_account`:r=!0;break}let i=[],a=[];return t&&(i.push({kind:`clear_peer_flags`,payload:{}}),a.push(`Clear SCAM / FAKE`)),n&&(i.push({kind:`unfreeze_account`,payload:{}}),a.push(`Unfreeze account`)),{actions:i,label:a.join(` + `)||`No recovery action needed`,blocked:r}}function ci(e){return{no_violation:`No violation (dismiss report)`,scam:`Mark as SCAM`,fake:`Mark as FAKE`,freeze:`Freeze account`,scam_freeze:`SCAM + freeze`,fake_freeze:`FAKE + freeze`,delete_messages:`Delete messages covered by evidence`,delete_account:`Delete account`}[e]}function li({label:e,sortKey:t,activeKey:n,desc:r,onSort:i}){return(0,W.jsx)(`th`,{children:(0,W.jsxs)(`button`,{type:`button`,className:`sort-header`,onClick:()=>i(t),children:[e,t===n?r?(0,W.jsx)(ue,{size:13}):(0,W.jsx)(pe,{size:13}):(0,W.jsx)(L,{size:13,className:`sort-header-idle`})]})})}function ui(){let[e,t]=(0,g.useState)(null),[n,r]=(0,g.useState)([]),[i,a]=(0,g.useState)(!1),[o,s]=(0,g.useState)(0),[c,l]=(0,g.useState)(!1),[u,d]=(0,g.useState)(``),[f,p]=(0,g.useState)(``),[m,h]=(0,g.useState)(`bytes`),[_,v]=(0,g.useState)(!0);async function y(){try{t(await k.storageStats())}catch{}}async function b(e=!1){l(!0),d(``);let t=new URLSearchParams({limit:`50`,offset:String(e?o:0),sort:m,order:_?`desc`:`asc`});f.trim()&&t.set(`q`,f.trim());try{let n=await k.storageAccounts(t),i=n.rows??[];r(t=>e?[...t,...i]:i),s(n.next_offset),a(!!n.has_more)}catch(e){d(O(e))}finally{l(!1)}}function x(){y(),b(!1)}(0,g.useEffect)(()=>{x()},[m,_]);function S(e){e===m?v(e=>!e):(h(e),v(!0))}let C=e?Math.max(0,Number(e.LogicalBytes)-Number(e.PhysicalBytes)):0;return(0,W.jsxs)(W.Fragment,{children:[u&&(0,W.jsx)(q,{children:u}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Physical usage (on disk / S3)`,value:e?Pt(e.PhysicalBytes):`-`}),(0,W.jsx)(Y,{label:`Logical usage (sum per account)`,value:e?Pt(e.LogicalBytes):`-`}),(0,W.jsx)(Y,{label:`Saved by dedup`,value:Pt(String(C)),tone:C>0?`good`:`neutral`}),(0,W.jsx)(Y,{label:`Backend`,value:e?.BackendKind??`-`})]}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Documents`,value:e?Dt(e.DocumentCount):`-`}),(0,W.jsx)(Y,{label:`Photos`,value:e?Dt(e.PhotoCount):`-`}),(0,W.jsx)(Y,{label:`Accounts with media`,value:e?Dt(e.AccountCount):`-`}),(0,W.jsx)(Y,{label:`Unattributed`,value:e?Pt(e.UnattributedBytes):`-`,tone:e&&Number(e.UnattributedBytes)>0?`warn`:`neutral`})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),b(!1)},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:f,onChange:e=>p(e.target.value),placeholder:`User ID / username / name`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:c,children:[c?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:x,disabled:c,children:[(0,W.jsx)(B,{size:15,className:c?`spin`:``}),` `,`Refresh`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(li,{label:`User ID`,sortKey:`user_id`,activeKey:m,desc:_,onSort:S}),(0,W.jsx)(li,{label:`Account`,sortKey:`username`,activeKey:m,desc:_,onSort:S}),(0,W.jsx)(li,{label:`Storage used`,sortKey:`bytes`,activeKey:m,desc:_,onSort:S}),(0,W.jsx)(li,{label:`Files`,sortKey:`files`,activeKey:m,desc:_,onSort:S})]})}),(0,W.jsxs)(`tbody`,{children:[n.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:e.UserID}),(0,W.jsx)(`td`,{children:H(e.Username)||e.FirstName||`-`}),(0,W.jsx)(`td`,{className:`mono`,children:Pt(e.Bytes)}),(0,W.jsx)(`td`,{className:`mono`,children:Dt(e.FileCount)})]},e.UserID)),n.length===0&&(0,W.jsx)(Bt,{colSpan:4})]})]})}),i&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>b(!0),disabled:c,children:[c?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})})]})}function di({navigate:e}){let[t,n]=(0,g.useState)(`overview`);return(0,W.jsxs)(G,{title:`Storage`,eyebrow:`Media / Storage usage`,children:[(0,W.jsxs)(`div`,{className:`tab-bar`,role:`tablist`,"aria-label":`Storage sections`,children:[(0,W.jsx)(`button`,{className:`tab-btn ${t===`overview`?`active`:``}`,type:`button`,role:`tab`,"aria-selected":t===`overview`,onClick:()=>n(`overview`),children:`Overview`}),(0,W.jsx)(`button`,{className:`tab-btn ${t===`limits`?`active`:``}`,type:`button`,role:`tab`,"aria-selected":t===`limits`,onClick:()=>n(`limits`),children:`Limits & Retention`})]}),t===`overview`?(0,W.jsx)(ui,{}):(0,W.jsx)(xi,{})]})}var fi={maxTotal:`TELESRV_STORAGE_MAX_TOTAL_BYTES`,minFree:`TELESRV_STORAGE_MIN_FREE_BYTES`,maxUploadFile:`TELESRV_STORAGE_MAX_UPLOAD_FILE_BYTES`,retentionMode:`TELESRV_STORAGE_RETENTION_MODE`,retentionMaxAge:`TELESRV_STORAGE_RETENTION_MAX_AGE`},pi=524288*8e3,mi=[{label:`MB`,bytes:1024**2},{label:`GB`,bytes:1024**3},{label:`TB`,bytes:1024**4}];function hi(e){for(let t=mi.length-1;t>=0;t--)if(e>=mi[t].bytes)return mi[t];return mi[1]}function gi(e){let t=0,n=/(\d+(?:\.\d+)?)\s*(h|m|s)/g,r;for(;(r=n.exec(e))!==null;){let e=parseFloat(r[1]),n=r[2];t+=n===`h`?e*3600:n===`m`?e*60:e}return t/60}var _i=[{label:`Minutes`,minutes:1},{label:`Hours`,minutes:60},{label:`Days`,minutes:1440}];function vi(e){for(let t=_i.length-1;t>=0;t--)if(e>=_i[t].minutes)return _i[t];return _i[0]}function yi({label:e,help:t,minutes:n,disabled:r,onChange:i}){let a=Number(n||`0`),[o,s]=(0,g.useState)(()=>vi(a).label),c=_i.find(e=>e.label===o)??_i[2],l=a>0?a/c.minutes:NaN;function u(e){let t=Number(e);if(!e.trim()||Number.isNaN(t)||t<=0){i(`0`);return}i(String(Math.max(1,Math.round(t*c.minutes))))}return(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:e}),(0,W.jsxs)(`div`,{style:{display:`flex`,gap:8},children:[(0,W.jsx)(`input`,{type:`number`,min:`0`,step:`any`,value:Number.isNaN(l)?``:l,disabled:r,onChange:e=>u(e.target.value)}),(0,W.jsx)(`select`,{value:o,disabled:r,onChange:e=>s(e.target.value),style:{maxWidth:100},children:_i.map(e=>(0,W.jsx)(`option`,{value:e.label,children:e.label},e.label))})]}),(0,W.jsx)(`span`,{className:`env-field-desc`,children:t})]})}function bi({label:e,help:t,bytes:n,onChange:r}){let i=Number(n||`0`),[a,o]=(0,g.useState)(()=>hi(i).label),s=mi.find(e=>e.label===a)??mi[1],c=i>0?i/s.bytes:NaN;function l(e){let t=Number(e);if(!e.trim()||Number.isNaN(t)||t<=0){r(`0`);return}r(String(Math.round(t*s.bytes)))}return(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:e}),(0,W.jsxs)(`div`,{style:{display:`flex`,gap:8},children:[(0,W.jsx)(`input`,{type:`number`,min:`0`,step:`any`,value:Number.isNaN(c)?``:c,placeholder:`Unlimited`,onChange:e=>l(e.target.value)}),(0,W.jsx)(`select`,{value:a,onChange:e=>o(e.target.value),style:{maxWidth:90},children:mi.map(e=>(0,W.jsx)(`option`,{value:e.label,children:e.label},e.label))})]}),(0,W.jsx)(`span`,{className:`env-field-desc`,children:t})]})}function xi(){let[e,t]=(0,g.useState)(!1),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)({}),[o,s]=(0,g.useState)(`0`),[c,l]=(0,g.useState)(`0`),[u,d]=(0,g.useState)(`0`),[f,p]=(0,g.useState)(`off`),[m,h]=(0,g.useState)(`43200`);async function _(){r(``);try{let e=await k.serverEnv(),n={};for(let t of e)for(let e of t.fields)n[e.key]=e.value||e.default_value||``;a(n),s(n[fi.maxTotal]||`0`),l(n[fi.minFree]||`0`),d(n[fi.maxUploadFile]||`0`);let r=(n[fi.retentionMode]||`off`).trim().toLowerCase();p(r===`orphan`||r===`hard`?r:`off`);let i=gi(n[fi.retentionMaxAge]||`720h`);h(i>0?String(Math.max(1,Math.round(i))):`43200`),t(!0)}catch(e){r(O(e))}}(0,g.useEffect)(()=>{_()},[]);let v=(0,g.useMemo)(()=>{let e={[fi.maxTotal]:o||`0`,[fi.minFree]:c||`0`,[fi.maxUploadFile]:u||`0`,[fi.retentionMode]:f,[fi.retentionMaxAge]:f===`off`?i[fi.retentionMaxAge]||`720h`:`${Math.max(1,Math.round(Number(m||`0`)))}m`},t={};for(let[n,r]of Object.entries(e))(i[n]??``)!==r&&(t[n]=r);return t},[o,c,u,f,m,i]),y=Object.keys(v).length>0,b=Number(u||`0`)>pi;return(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Limits & Retention`,text:`Server-wide storage budget, per-file upload cap, and automatic media cleanup. Saved to .env -- takes effect on the next Restart/Update.`}),n&&(0,W.jsx)(q,{children:n}),e?(0,W.jsxs)(`div`,{className:`card-body`,children:[(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsx)(bi,{label:`Max total storage budget`,help:`Reject new uploads once total tracked blob bytes would exceed this. Empty/0 = unlimited.`,bytes:o,onChange:s}),(0,W.jsx)(bi,{label:`Min free space guard`,help:`localfs backend only: reject new uploads once real free disk space falls below this. Empty/0 disables the check.`,bytes:c,onChange:l}),(0,W.jsx)(bi,{label:`Max single file size`,help:`Reject a single upload once its total assembled size exceeds this. Empty/0 = unlimited, bounded only by the protocol's own ~4GB per-file ceiling.`,bytes:u,onChange:d}),b&&(0,W.jsx)(q,{children:`This exceeds the protocol's own ~4GB upload ceiling and will be refused when the server restarts.`})]}),(0,W.jsxs)(`div`,{className:`attr-block`,style:{marginTop:`1.5em`},children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Retention mode`}),(0,W.jsxs)(`select`,{value:f,onChange:e=>p(e.target.value),children:[(0,W.jsx)(`option`,{value:`off`,children:`Off`}),(0,W.jsx)(`option`,{value:`orphan`,children:`Delete once no longer used (safe)`}),(0,W.jsx)(`option`,{value:`hard`,children:`Delete after a fixed time, even if still in use`})]})]}),(0,W.jsxs)(`p`,{className:`env-field-desc`,children:[f===`off`&&`No storage sweep runs; nothing is auto-deleted. Storage usage is still tracked and shown above either way.`,f===`orphan`&&`Safe: a document or photo's file is deleted only once it is no longer referenced by any message, profile photo, or sticker set. Media still visible in a conversation is never touched, regardless of age.`,f===`hard`&&`Irreversible and aggressive: a document or photo's file bytes are deleted once old enough, REGARDLESS of whether a message still references it. Old media in active conversations will start showing as unavailable once purged -- only the file is removed, the message itself keeps rendering its placeholder (name, size, thumbnail).`]}),(0,W.jsx)(yi,{label:`Retention age`,help:f===`hard`?`How old the media itself must be, counted from when it was uploaded, before its bytes are purged.`:`How long a document or photo must have had zero references before its file is deleted.`,minutes:m,disabled:f===`off`,onChange:h})]}),(0,W.jsx)(`div`,{className:`gift-table-actions env-save-row`,children:(0,W.jsx)(Z,{tone:`warn`,label:`Save limits & retention settings`,path:`/api/actions/update-server-env`,payload:()=>({values:v}),disabled:!y||b,onDone:()=>void _()})})]}):(0,W.jsx)(`p`,{style:{color:`var(--muted)`},children:`Loading current settings...`})]})}function Si({loader:e,cacheKey:t,className:n,playOnHover:r=!0,onError:i}){let a=(0,g.useRef)(null),o=(0,g.useRef)(null);(0,g.useEffect)(()=>{let t=!1;return e().then(e=>{t||!a.current||(o.current?.destroy(),o.current=Sr.default.loadAnimation({container:a.current,renderer:`canvas`,loop:!0,autoplay:!1,animationData:structuredClone(e)}),o.current.goToAndStop(0,!0))}).catch(()=>i?.()),()=>{t=!0,o.current?.destroy(),o.current=null}},[t]);function s(){r&&o.current?.play()}function c(){r&&o.current?.goToAndStop(0,!0)}return(0,W.jsx)(`div`,{className:n,ref:a,onMouseEnter:s,onMouseLeave:c})}function Ci(e){let t=e.toLowerCase();return t.includes(`tgsticker`)||t.includes(`lottie`)||t.includes(`json`)}function wi({row:e}){let[t,n]=(0,g.useState)(!Ci(e.MimeType));return(0,g.useEffect)(()=>{n(!Ci(e.MimeType))},[e.DocumentID,e.MimeType]),t?(0,W.jsx)(`div`,{className:`emoji-picker-glyph`,children:e.Alt||`🙂`}):(0,W.jsx)(Si,{className:`emoji-picker-anim`,cacheKey:e.DocumentID,loader:()=>k.emojiAnimation(e.DocumentID),onError:()=>n(!0)})}function Ti({label:e,value:t,onChange:n}){let[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)([]),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(``),d=a.find(e=>e.DocumentID===t)??null;async function f(){c(!0),u(``);let e=new URLSearchParams({limit:`24`});r.trim()&&e.set(`q`,r.trim());try{o((await k.emoji(e)).rows??[])}catch(e){u(O(e))}finally{c(!1)}}return(0,g.useEffect)(()=>{f()},[]),(0,W.jsxs)(`div`,{className:`entity-picker`,children:[(0,W.jsxs)(`div`,{className:`picker-head`,children:[(0,W.jsx)(`span`,{children:e}),t?(0,W.jsxs)(`button`,{className:`link-button`,type:`button`,onClick:()=>n(``),children:[(0,W.jsx)(yt,{size:13}),` `,`Clear`]}):null]}),t?(0,W.jsxs)(`div`,{className:`selected-entity`,children:[(0,W.jsx)(ye,{size:15}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`strong`,{children:d?.Alt||`—`}),(0,W.jsx)(`span`,{className:`mono`,children:t})]}),(0,W.jsx)(`span`,{children:d?.SetTitle||`-`})]}):null,(0,W.jsxs)(`div`,{className:`picker-search`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),f())},placeholder:`Search document ID or emoji`}),(0,W.jsx)(`button`,{className:`btn compact-btn`,type:`button`,onClick:f,disabled:s,children:s?(0,W.jsx)(I,{size:14,className:`spin`}):`Search`})]}),l&&(0,W.jsx)(`div`,{className:`picker-error`,children:l}),(0,W.jsxs)(`div`,{className:`picker-results emoji-picker-results`,children:[a.map(e=>(0,W.jsxs)(`button`,{className:`picker-row emoji-picker-row ${t===e.DocumentID?`selected`:``}`,type:`button`,onClick:()=>n(e.DocumentID),children:[(0,W.jsx)(wi,{row:e}),(0,W.jsx)(`span`,{className:`mono`,children:e.DocumentID}),(0,W.jsx)(`span`,{children:e.SetTitle||`—`})]},e.DocumentID)),a.length===0&&!s?(0,W.jsx)(`div`,{className:`picker-empty`,children:`No results`}):null]})]})}var Ei=[`pending`,`approved`,`rejected`,`revoked`],Di=[`user`,`channel`],Oi={pending:`Pending`,approved:`Approved`,rejected:`Rejected`,revoked:`Mark revoked`},ki={user:`Account`,channel:`Channel`};function Ai({navigate:e}){let{can:t}=Xt(),n=t(Kt),r=t(Wt),[i,a]=(0,g.useState)(`requests`),[o,s]=(0,g.useState)([]),[c,l]=(0,g.useState)([]),[u,d]=(0,g.useState)(``),[f,p]=(0,g.useState)(!1);async function m(){d(``),p(!1);try{let[e,t]=await Promise.all([k.botVerifiers(new URLSearchParams({limit:`200`})),k.verificationIcons(new URLSearchParams({limit:`200`}))]);s(e.rows??[]),l(t.rows??[])}catch(e){if(e instanceof v&&e.status===403){s([]),l([]),p(!0);return}d(O(e))}}return(0,g.useEffect)(()=>{m()},[]),(0,W.jsxs)(G,{title:`Third-party verification`,eyebrow:`Third-party verification / Verifiers, icons, marks`,actions:r?(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>e(`/verification`),children:[(0,W.jsx)(De,{size:15}),` `,`Official verification`]}):void 0,children:[u&&(0,W.jsx)(q,{children:u}),f&&(0,W.jsx)(q,{children:`The server refused the verifier roster and the icon catalogue for this session (403), so both lists are empty here — applications can still be reviewed.`}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`A verifier company's icon — not the official checkmark`,text:`A third-party mark is a verifier bot's own icon, drawn right BEFORE the name of an account, a bot or a channel, plus one line of description in the profile. It says “this verifier vouches for this peer”, and nothing more.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The icon is a custom emoji document. The client fetches it through messages.getCustomEmojiDocuments, so a document id that resolves to nothing renders as no badge at all — which is why marks are granted from the catalogue below rather than from a typed number.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The official checkmark is a different mechanism, granted by the platform in the Verification section. The two are stored, shown and taken away separately, and neither one implies the other.`}),!n&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This session can read the section and decide applications, but not change verifiers or the icon catalogue — that needs the botverification.manage permission.`})]}),(0,W.jsx)(`div`,{className:`toolbar`,role:`group`,"aria-label":`Third-party verification`,children:[{key:`requests`,label:`Applications`,icon:(0,W.jsx)(ut,{size:15})},{key:`verifiers`,label:`Verifiers`,icon:(0,W.jsx)(_e,{size:15})},{key:`icons`,label:`Icon catalogue`,icon:(0,W.jsx)(dt,{size:15})},{key:`marks`,label:`Granted marks`,icon:(0,W.jsx)(ee,{size:15})}].map(e=>(0,W.jsxs)(`button`,{className:`btn icon-text ${i===e.key?`primary`:``}`,type:`button`,"aria-pressed":i===e.key,onClick:()=>a(e.key),children:[e.icon,` `,e.label]},e.key))}),i===`requests`&&(0,W.jsx)(ji,{navigate:e,verifiers:o}),i===`verifiers`&&(0,W.jsx)(Mi,{verifiers:o,icons:c,canManage:n,onChanged:m,navigate:e}),i===`icons`&&(0,W.jsx)(Ni,{icons:c,verifiers:o,canManage:n,onChanged:m}),i===`marks`&&(0,W.jsx)(Pi,{verifiers:o,canManage:n,navigate:e})]})}function ji({navigate:e,verifiers:t}){let[n,r]=(0,g.useState)(`pending`),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(`all`),[c,l]=(0,g.useState)(``),[u,d]=(0,g.useState)(`50`),[f,p]=(0,g.useState)([]),[m,h]=(0,g.useState)({}),[_,v]=(0,g.useState)(!1),[y,b]=(0,g.useState)(``),[x,S]=(0,g.useState)(!1),[C,w]=(0,g.useState)(``);async function T(e=!1){S(!0),w(``);let t=new URLSearchParams({limit:u});n!==`all`&&t.set(`status`,n),i&&t.set(`verifier_bot_id`,i),o!==`all`&&t.set(`peer_type`,o),c.trim()&&t.set(`q`,c.trim().replace(/^@/,``)),e&&y&&t.set(`before_id`,y);try{let n=await k.customVerificationRequests(t),r=n.rows??[];p(t=>e?[...t,...r]:r),b(n.next_before_id??``),v(!!n.has_more)}catch(e){w(O(e))}finally{S(!1)}}async function E(){try{h((await k.botVerificationCounts()).counts??{})}catch(e){w(O(e))}}(0,g.useEffect)(()=>{T(!1),E()},[]);function D(){T(!1),E()}return(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Application queue`,text:`Applications filed with a verifier bot by the owner of the peer. The counters cover the whole queue, not the page below.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:D,disabled:x,children:[(0,W.jsx)(B,{size:15,className:x?`spin`:``}),` `,`Refresh`]})}),C&&(0,W.jsx)(q,{children:C}),(0,W.jsx)(`div`,{className:`metric-row`,children:Ei.map(e=>(0,W.jsx)(Y,{label:Oi[e],value:m[e]??`0`,mono:!0,tone:Ri(e,m[e]??`0`)},e))})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),T(!1)},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:c,onChange:e=>l(e.target.value),placeholder:`Application id, peer id, username or title`})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Status`}),(0,W.jsxs)(`select`,{value:n,onChange:e=>r(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All statuses`}),Ei.map(e=>(0,W.jsx)(`option`,{value:e,children:Oi[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Verifier`}),(0,W.jsx)(Fi,{value:i,verifiers:t,onChange:a})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Peer type`}),(0,W.jsxs)(`select`,{value:o,onChange:e=>s(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All types`}),Di.map(e=>(0,W.jsx)(`option`,{value:e,children:ki[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsx)(`input`,{className:`small-input`,value:u,onChange:e=>d(e.target.value),type:`number`,min:`1`,max:`200`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:x,children:[x?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Verifier`}),(0,W.jsx)(`th`,{children:`Peer`}),(0,W.jsx)(`th`,{children:`Applicant`}),(0,W.jsx)(`th`,{children:`Stated reason`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Filed`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[f.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:(0,W.jsxs)(`button`,{className:`row-link`,type:`button`,onClick:()=>e(`/bot-verification/${t.ID}`),children:[`#`,t.ID]})}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:H(t.VerifierBotUsername)||t.VerifierBotID}),(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:t.VerifierBotID})]}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:zi(t)}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[ki[t.PeerType],` · `,t.PeerID]})]}),(0,W.jsxs)(`td`,{children:[H(t.ApplicantUsername)||`-`,(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:t.ApplicantUserID})]}),(0,W.jsx)(`td`,{className:`truncate`,children:t.Reason||`-`}),(0,W.jsx)(`td`,{children:(0,W.jsx)(Ii,{status:t.Status})}),(0,W.jsx)(`td`,{children:U(t.CreatedAt)||`-`}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,type:`button`,onClick:()=>e(`/bot-verification/${t.ID}`),children:[(0,W.jsx)(ut,{size:14}),` `,`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),f.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})}),_&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>T(!0),disabled:x,children:[x?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})})]})}function Mi({verifiers:e,icons:t,canManage:n,onChanged:r,navigate:i}){let[a,o]=(0,g.useState)(null),[s,c]=(0,g.useState)(null),[l,u]=(0,g.useState)(``),[d,f]=(0,g.useState)(``),[p,m]=(0,g.useState)(``),[h,_]=(0,g.useState)(!1),v=t.filter(e=>e.Active),y=v.map(e=>({value:e.DocumentID,label:`${e.Name} · ${e.DocumentID}`}));if(l&&!y.some(e=>e.value===l)){let e=t.find(e=>e.DocumentID===l);y.unshift({value:l,label:`${e?.Name??l} · ${l} (Retired)`})}function b(e){c(e),o(null),u(e.IconDocumentID),f(e.CompanyName),m(e.DefaultDescription),_(e.CanModifyCustomDescription)}function x(){c(null),o(null),u(``),f(``),m(``),_(!1)}function S(){return{bot_id:s?s.BotID:a?String(a.ID):`0`,icon_document_id:l||`0`,company_name:d.trim(),default_description:p.trim(),can_modify_custom_description:h,version:s?s.Version:`0`}}return(0,W.jsxs)(W.Fragment,{children:[n&&(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:s?`Update verifier`:`Grant verifier status`,text:`The bot gets an icon from the catalogue and a company name to vouch under. The same call updates an existing verifier, which is why it carries a version.`,action:s?(0,W.jsx)(`button`,{className:`btn icon-text`,type:`button`,onClick:x,children:`Cancel update`}):void 0}),s?(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Updating ${H(s.BotUsername)||s.BotID} — version ${s.Version} is sent as the optimistic lock, so a row somebody else changed meanwhile is refused instead of overwritten.`}):(0,W.jsx)(qn,{label:`Bot`,value:a,onChange:o}),(0,W.jsxs)(`div`,{className:`bot-create-fields`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Icon from the catalogue`}),(0,W.jsxs)(`select`,{value:l,onChange:e=>u(e.target.value),children:[(0,W.jsx)(`option`,{value:``,children:`Pick an icon`}),y.map(e=>(0,W.jsx)(`option`,{value:e.value,children:e.label},e.value))]})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Company`}),(0,W.jsx)(`input`,{value:d,onChange:e=>f(e.target.value),placeholder:`Acme Verification Ltd`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Default description`}),(0,W.jsx)(`input`,{value:p,onChange:e=>m(e.target.value),placeholder:`Verified by Acme`})]})]}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:h,onChange:e=>_(e.target.checked)}),`The verifier may replace the description per peer`]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This is botVerifierSettings.can_modify_custom_description: with it off, every mark this verifier grants carries the default description above, whatever the applicant asked for.`}),v.length===0&&(0,W.jsx)(q,{children:`The catalogue has no active icon, so there is nothing to grant. Add one in the icon catalogue first.`}),(0,W.jsxs)(`div`,{className:`bot-create-actions`,children:[(0,W.jsx)(`span`,{className:`bot-create-note`,children:`The bot can mark peers as soon as the row exists and is enabled.`}),(0,W.jsx)(Z,{label:s?`Update verifier`:`Grant verifier status`,icon:(0,W.jsx)(Ye,{size:15}),tone:`neutral`,path:`/api/actions/grant-bot-verifier`,payload:S,onDone:()=>{x(),r()}})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Verifier bots`,text:`Bots allowed to hand out their own mark. Verifier status is granted per deployment, so every row here is a badge printer an operator switched on by hand.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:r,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Bot`}),(0,W.jsx)(`th`,{children:`Company`}),(0,W.jsx)(`th`,{children:`Icon`}),(0,W.jsx)(`th`,{children:`Own description`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Marks`}),(0,W.jsx)(`th`,{children:`Granted by`}),(0,W.jsx)(`th`,{children:`Updated`}),n&&(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[e.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`button`,{className:`row-link`,type:`button`,onClick:()=>i(`/bots/${e.BotID}`),children:(0,W.jsx)(`strong`,{children:H(e.BotUsername)||e.BotName||e.BotID})}),(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:e.BotID})]}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:e.CompanyName||`-`}),(0,W.jsx)(`div`,{className:`entity-subtitle truncate`,children:e.DefaultDescription||`Not set`})]}),(0,W.jsxs)(`td`,{children:[e.IconName||`-`,(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:e.IconDocumentID})]}),(0,W.jsx)(`td`,{children:e.CanModifyCustomDescription?`Yes`:`No`}),(0,W.jsx)(`td`,{children:e.Enabled?(0,W.jsx)(J,{tone:`good`,children:`Enabled`}):(0,W.jsx)(J,{tone:`warn`,children:`disabled`})}),(0,W.jsx)(`td`,{className:`mono`,children:String(e.MarkCount??`0`)}),(0,W.jsxs)(`td`,{children:[e.GrantedBy||`-`,(0,W.jsx)(`div`,{className:`entity-subtitle truncate`,children:e.GrantReason||`-`})]}),(0,W.jsx)(`td`,{children:U(e.UpdatedAt)||`-`}),n&&(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`row-actions`,children:[(0,W.jsx)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>b(e),children:`Edit`}),(0,W.jsx)(Z,{label:e.Enabled?`Disable`:`Enable`,icon:e.Enabled?(0,W.jsx)(Xe,{size:14}):(0,W.jsx)(Ze,{size:14}),tone:e.Enabled?`warn`:`neutral`,compact:!0,path:`/api/actions/set-bot-verifier-enabled`,payload:()=>({bot_id:e.BotID,enabled:!e.Enabled}),onDone:r}),(0,W.jsx)(Z,{label:`Revoke status`,icon:(0,W.jsx)(pt,{size:14}),tone:`danger`,compact:!0,path:`/api/actions/revoke-bot-verifier`,payload:()=>({bot_id:e.BotID}),onDone:r})]})})]},e.BotID)),e.length===0&&(0,W.jsx)(Bt,{colSpan:n?9:8})]})]})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Disabling is the per-verifier kill switch: the marks already granted keep rendering, but the bot can no longer mark anything new and its settings stop being projected into botInfo.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Revoking verifier status removes the row and every mark this verifier granted — the icon disappears from all of its peers at once.`})]})]})}function Ni({icons:e,verifiers:t,canManage:n,onChanged:r}){let[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(``);function u(){let e={document_id:i.trim()||`0`,name:o.trim()};return c&&(e.owner_bot_id=c),e}return(0,W.jsxs)(W.Fragment,{children:[n&&(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Add or rename an icon`,text:`Search and pick any custom-emoji document already on this deployment (including bundled/system ones). Adding an id that already exists renames it instead of duplicating it.`}),(0,W.jsx)(Ti,{label:`Document`,value:i,onChange:a}),(0,W.jsxs)(`div`,{className:`bot-create-fields`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Name`}),(0,W.jsx)(`input`,{value:o,onChange:e=>s(e.target.value),placeholder:`Acme blue tick`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Owner`}),(0,W.jsxs)(`select`,{value:c,onChange:e=>l(e.target.value),children:[(0,W.jsx)(`option`,{value:``,children:`Shared`}),t.map(e=>(0,W.jsx)(`option`,{value:e.BotID,children:`${e.CompanyName||e.BotID} · ${H(e.BotUsername)||e.BotID}`},e.BotID))]})]})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`A document id that resolves to nothing produces an invisible badge: the peer is marked in the database and the client draws nothing.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`A shared icon may be granted to any verifier; picking an owner reserves it for that one bot.`}),(0,W.jsxs)(`div`,{className:`bot-create-actions`,children:[(0,W.jsx)(`span`,{className:`bot-create-note`,children:`Adding an icon grants nothing by itself — it only makes the document available to grant.`}),(0,W.jsx)(Z,{label:`Save icon`,icon:(0,W.jsx)(Ye,{size:15}),tone:`neutral`,path:`/api/actions/upsert-verification-icon`,payload:u,onDone:()=>{a(``),s(``),l(``),r()}})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Icon catalogue`,text:`The custom emoji documents a verifier may mark with. Nothing else can be used as an icon, so the catalogue is where a wrong badge is prevented rather than fixed.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:r,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Document ID`}),(0,W.jsx)(`th`,{children:`Name`}),(0,W.jsx)(`th`,{children:`Owner`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Verifiers using it`}),(0,W.jsx)(`th`,{children:`Filed`}),n&&(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[e.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:e.DocumentID}),(0,W.jsx)(`td`,{children:(0,W.jsx)(`strong`,{children:e.Name||`-`})}),(0,W.jsx)(`td`,{children:e.OwnerBotID&&e.OwnerBotID!==`0`?(0,W.jsxs)(W.Fragment,{children:[H(e.OwnerBotUsername)||e.OwnerBotID,(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:e.OwnerBotID})]}):(0,W.jsx)(J,{children:`Shared`})}),(0,W.jsx)(`td`,{children:e.Active?(0,W.jsx)(J,{tone:`good`,children:`Active`}):(0,W.jsx)(J,{tone:`warn`,children:`Retired`})}),(0,W.jsx)(`td`,{className:`mono`,children:String(e.UsedByVerifiers??`0`)}),(0,W.jsx)(`td`,{children:U(e.CreatedAt)||`-`}),n&&(0,W.jsx)(`td`,{children:(0,W.jsx)(`div`,{className:`row-actions`,children:(0,W.jsx)(Z,{label:e.Active?`Retire`:`Activate`,icon:e.Active?(0,W.jsx)(Xe,{size:14}):(0,W.jsx)(Ze,{size:14}),tone:e.Active?`warn`:`neutral`,compact:!0,path:`/api/actions/set-verification-icon-active`,payload:()=>({icon_id:e.ID,active:!e.Active}),onDone:r})})})]},e.ID)),e.length===0&&(0,W.jsx)(Bt,{colSpan:n?7:6})]})]})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Retiring an icon stops it from being granted to anybody new. Marks already carrying it keep it: the icon is copied onto the mark when it is granted.`})]})]})}function Pi({verifiers:e,canManage:t,navigate:n}){let[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(`all`),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(`50`),[d,f]=(0,g.useState)([]),[p,m]=(0,g.useState)(!1),[h,_]=(0,g.useState)(``),[v,y]=(0,g.useState)(!1),[b,x]=(0,g.useState)(``);async function S(e=!1){y(!0),x(``);let t=new URLSearchParams({limit:l});r&&t.set(`verifier_bot_id`,r),a!==`all`&&t.set(`peer_type`,a),s.trim()&&t.set(`q`,s.trim().replace(/^@/,``)),e&&h&&t.set(`before_id`,h);try{let n=await k.customVerifications(t),r=n.rows??[];f(t=>e?[...t,...r]:r),_(n.next_before_id??``),m(!!n.has_more)}catch(e){x(O(e))}finally{y(!1)}}return(0,g.useEffect)(()=>{S(!1)},[]),(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Granted marks`,text:`Every peer currently carrying a third-party mark, whoever granted it — an operator decision, the verifier bot itself, or the peer's owner through bots.setCustomVerification.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>S(!1),disabled:v,children:[(0,W.jsx)(B,{size:15,className:v?`spin`:``}),` `,`Refresh`]})}),b&&(0,W.jsx)(q,{children:b})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),S(!1)},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:s,onChange:e=>c(e.target.value),placeholder:`Peer id, username or title`})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Verifier`}),(0,W.jsx)(Fi,{value:r,verifiers:e,onChange:i})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Peer type`}),(0,W.jsxs)(`select`,{value:a,onChange:e=>o(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All types`}),Di.map(e=>(0,W.jsx)(`option`,{value:e,children:ki[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsx)(`input`,{className:`small-input`,value:l,onChange:e=>u(e.target.value),type:`number`,min:`1`,max:`200`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:v,children:[v?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Verifier`}),(0,W.jsx)(`th`,{children:`Peer`}),(0,W.jsx)(`th`,{children:`Description`}),(0,W.jsx)(`th`,{children:`Icon`}),(0,W.jsx)(`th`,{children:`Filed`}),t&&(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[d.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsxs)(`td`,{className:`mono`,children:[`#`,e.ID]}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:e.CompanyName||H(e.VerifierBotUsername)||e.VerifierBotID}),(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:H(e.VerifierBotUsername)||e.VerifierBotID})]}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`button`,{className:`row-link`,type:`button`,onClick:()=>n(Bi(e.PeerType,e.PeerID)),children:(0,W.jsx)(`strong`,{children:zi(e)})}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[ki[e.PeerType],` · `,e.PeerID]})]}),(0,W.jsx)(`td`,{className:`truncate`,children:e.Description||`Not set`}),(0,W.jsx)(`td`,{className:`mono`,children:e.IconDocumentID}),(0,W.jsx)(`td`,{children:U(e.CreatedAt)||`-`}),t&&(0,W.jsx)(`td`,{children:(0,W.jsx)(`div`,{className:`row-actions`,children:(0,W.jsx)(Z,{label:`Remove mark`,icon:(0,W.jsx)(he,{size:14}),tone:`danger`,compact:!0,path:`/api/actions/revoke-custom-verification`,payload:()=>({verifier_bot_id:e.VerifierBotID,peer_type:e.PeerType,peer_id:e.PeerID}),onDone:()=>S(!1)})})})]},e.ID)),d.length===0&&(0,W.jsx)(Bt,{colSpan:t?7:6})]})]})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Removing a mark clears the icon and the description from the peer. The application it came from keeps its history.`}),p&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>S(!0),disabled:v,children:[v?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})})]})}function Fi({value:e,verifiers:t,onChange:n}){return(0,W.jsxs)(`select`,{value:e,onChange:e=>n(e.target.value),children:[(0,W.jsx)(`option`,{value:``,children:`All verifiers`}),t.map(e=>(0,W.jsx)(`option`,{value:e.BotID,children:`${e.CompanyName||e.BotID} · ${H(e.BotUsername)||e.BotID}`+(e.Enabled?``:` (disabled)`)},e.BotID))]})}function Ii({status:e}){return(0,W.jsx)(J,{tone:Li(e),children:Oi[e]})}function Li(e){return e===`approved`?`good`:e===`pending`?`warn`:e===`rejected`?`danger`:`neutral`}function Ri(e,t){return e===`pending`?t!==`0`&&t!==``?`warn`:`neutral`:e===`approved`?`good`:`neutral`}function zi(e){return H(e.PeerUsername)||e.PeerTitle||`#${e.PeerID}`}function Bi(e,t){return e===`channel`?`/channels/${t}`:`/accounts/${t}`}function Vi({id:e,navigate:t}){let[n,r]=(0,g.useState)(null),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(!1),[c,l]=(0,g.useState)(!1),[u,d]=(0,g.useState)(``);async function f(){l(!0),d(``);try{r(await k.customVerificationRequest(e))}catch(e){d(O(e))}finally{l(!1)}}function p(){s(!1),f()}(0,g.useEffect)(()=>{f()},[e]);function m(e){if(e instanceof v&&e.status===409)return s(!0),f(),`Another admin has already changed this application. The data has been reloaded — check the status before deciding again.`}if(u&&!n)return(0,W.jsx)(q,{children:u});if(!n)return(0,W.jsx)(Vt,{label:`Loading the application…`});let h=n.request,_=Ui(n.verifier),y=n.mark_active,b=h.Status===`pending`,x=h.Status===`approved`,S=i.trim(),C=h.RequestedDescription.trim(),w=!!_?.CanModifyCustomDescription&&C!==``,T=w?C:(_?.DefaultDescription??``).trim();function E(){let e={version:h.Version};return S&&(e.internal_note=S),e}function D(){a(``),s(!1),f()}return(0,W.jsxs)(G,{title:`Application #${h.ID}`,eyebrow:`Third-party verification / Review`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/bot-verification`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to list`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:p,disabled:c,children:[(0,W.jsx)(B,{size:15,className:c?`spin`:``}),` `,`Refresh`]})]}),children:[u&&(0,W.jsx)(q,{children:u}),o&&(0,W.jsx)(q,{children:`Another admin has already changed this application. The data has been reloaded — check the status before deciding again.`}),(0,W.jsx)(Rt,{main:(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:zi(h)}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[`#`,h.ID,` · `,ki[h.PeerType],`:`,h.PeerID,` · v`,h.Version]})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[(0,W.jsx)(Ii,{status:h.Status}),y?(0,W.jsxs)(J,{tone:`good`,children:[(0,W.jsx)(ee,{size:12}),` `,`Mark is live`]}):(0,W.jsx)(J,{tone:`neutral`,children:`No mark on the peer`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`A verifier company's icon — not the official checkmark`,text:`A third-party mark is a verifier bot's own icon, drawn right BEFORE the name of an account, a bot or a channel, plus one line of description in the profile. It says “this verifier vouches for this peer”, and nothing more.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The icon is a custom emoji document. The client fetches it through messages.getCustomEmojiDocuments, so a document id that resolves to nothing renders as no badge at all — which is why marks are granted from the catalogue below rather than from a typed number.`})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Verifier`,text:`The company whose icon the peer would carry, as its row stands right now.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/bots/${h.VerifierBotID}`),children:[(0,W.jsx)(_e,{size:15}),` `,`Open verifier bot`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Company`,value:_?.CompanyName||`-`}),(0,W.jsx)(X,{label:`Bot`,value:H(h.VerifierBotUsername)||`-`}),(0,W.jsx)(X,{label:`Verifier bot ID`,value:h.VerifierBotID,mono:!0}),(0,W.jsx)(X,{label:`Document ID`,value:_?.IconDocumentID||`-`,mono:!0}),(0,W.jsx)(X,{label:`Name`,value:_?.IconName||`-`}),(0,W.jsx)(X,{label:`Own description`,value:_?.CanModifyCustomDescription?`Yes`:`No`})]}),(0,W.jsx)(Hi,{label:`Default description`,children:_?.DefaultDescription?(0,W.jsx)(`p`,{className:`about-text`,children:_.DefaultDescription}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})}),!_&&(0,W.jsx)(q,{children:`The verifier row is gone: its status was revoked after this application was filed. There is no icon to grant, so the application can only be rejected.`}),_&&!_.Enabled&&(0,W.jsx)(q,{children:`This verifier is disabled. It cannot mark anything new until an operator enables it again.`})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Peer`,text:`The account, bot or channel the icon would be attached to.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(Bi(h.PeerType,h.PeerID)),children:[(0,W.jsx)(De,{size:15}),` `,`Open peer`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Type`,value:ki[h.PeerType]}),(0,W.jsx)(X,{label:`Username`,value:H(h.PeerUsername)||`-`}),(0,W.jsx)(X,{label:`Title`,value:h.PeerTitle||`-`}),(0,W.jsx)(X,{label:`Peer ID`,value:h.PeerID,mono:!0})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Applicant`,text:`Who filed the application with the verifier bot.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/accounts/${h.ApplicantUserID}`),children:[(0,W.jsx)(gt,{size:15}),` `,`Open account`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Username`,value:H(h.ApplicantUsername)||`-`}),(0,W.jsx)(X,{label:`User ID`,value:h.ApplicantUserID,mono:!0}),(0,W.jsx)(X,{label:`Filed`,value:U(h.CreatedAt)||`-`}),(0,W.jsx)(X,{label:`Updated`,value:U(h.UpdatedAt)||`-`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Application`,text:`What the applicant wrote, rendered as plain text.`}),(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Correlation ID`,value:h.CorrelationID||`-`,mono:!0}),(0,W.jsx)(X,{label:`Status`,value:Oi[h.Status]})]}),(0,W.jsx)(Hi,{label:`Stated reason`,children:h.Reason?(0,W.jsx)(`p`,{className:`about-text`,children:h.Reason}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})}),(0,W.jsx)(Hi,{label:`Requested description`,children:C?(0,W.jsx)(`p`,{className:`about-text`,children:C}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})}),(0,W.jsx)(Hi,{label:`Description the mark would carry`,children:T?(0,W.jsx)(`p`,{className:`about-text`,children:T}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Resolved the same way the backend resolves it: the applicant's wording only when this verifier may set its own description, otherwise the verifier's default.`}),C!==``&&!w&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This verifier may not set a per-peer description, so the requested wording is ignored and the default is applied.`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Decision`,text:`What was decided, by whom, and with which wording.`}),(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Decided by`,value:h.DecidedBy||`-`}),(0,W.jsx)(X,{label:`Approved`,value:U(h.ApprovedAt)||`-`}),(0,W.jsx)(X,{label:`Rejected`,value:U(h.RejectedAt)||`-`}),(0,W.jsx)(X,{label:`Version (optimistic lock)`,value:h.Version,mono:!0})]}),(0,W.jsx)(Hi,{label:`Decision reason`,children:h.DecisionReason?(0,W.jsx)(`p`,{className:`about-text`,children:h.DecisionReason}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`No decision yet`})}),(0,W.jsx)(Hi,{label:`Internal note · admins only`,children:h.InternalNote?(0,W.jsx)(`p`,{className:`about-text`,children:h.InternalNote}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})})]})]})]}),side:(0,W.jsxs)(`section`,{className:`action-dock`,children:[(0,W.jsxs)(`div`,{className:`dock-title`,children:[(0,W.jsx)(ut,{size:14}),` `,`Decision`]}),!b&&!x&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This status has no available actions.`}),(b||x)&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Internal note`}),(0,W.jsx)(`textarea`,{value:i,onChange:e=>a(e.target.value),rows:3,placeholder:`Handover note for other admins`})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Optional. Stored with the decision and visible to admins only — never sent to the applicant.`})]}),b&&(0,W.jsxs)(W.Fragment,{children:[!_&&(0,W.jsx)(q,{children:`The verifier row is gone: its status was revoked after this application was filed. There is no icon to grant, so the application can only be rejected.`}),_&&!_.Enabled&&(0,W.jsx)(q,{children:`This verifier is disabled. It cannot mark anything new until an operator enables it again.`}),y&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This peer already carries this verifier's mark; approving refreshes the description and records the decision.`}),(0,W.jsxs)(`div`,{className:`action-stack`,children:[(0,W.jsx)(Z,{label:`Approve`,icon:(0,W.jsx)(F,{size:15}),tone:`neutral`,path:`/api/botverification/requests/${h.ID}/approve`,payload:E,onDone:D,onError:m}),(0,W.jsx)(Z,{label:`Reject`,icon:(0,W.jsx)(ne,{size:15}),tone:`warn`,path:`/api/botverification/requests/${h.ID}/reject`,payload:E,onDone:D,onError:m})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Puts the verifier's icon before the peer's name and its description in the profile, and messages the applicant.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The reason is mandatory: it is the wording the applicant is told, so write what exactly was missing.`})]}),x&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`div`,{className:`dock-title`,children:[(0,W.jsx)(st,{size:14}),` `,`Danger zone`]}),(0,W.jsxs)(`div`,{className:`danger-zone`,children:[(0,W.jsx)(Z,{label:`Revoke mark`,icon:(0,W.jsx)(he,{size:15}),tone:`danger`,path:`/api/botverification/requests/${h.ID}/revoke`,payload:E,onDone:D,onError:m}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Takes the icon and the description off the peer and closes the application as revoked. The official checkmark, if the peer has one, is untouched.`}),!y&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The peer carries no mark right now — revoking only closes the application.`})]})]})]})})]})}function Hi({label:e,children:t}){return(0,W.jsxs)(`div`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:e}),t]})}function Ui(e){return!e||!e.BotID||e.BotID===`0`?null:e}var Wi=[`draft`,`submitted`,`in_review`,`approved`,`rejected`,`cancelled`],Gi=[`bot`,`channel`,`supergroup`,`user`],Ki={draft:`Draft`,submitted:`Submitted`,in_review:`In review`,approved:`Approved`,rejected:`Rejected`,cancelled:`Cancelled`},qi={bot:`Bot`,channel:`Channel`,supergroup:`Supergroup`,user:`User`};function Ji({navigate:e}){let[t,n]=(0,g.useState)(`all`),[r,i]=(0,g.useState)(`all`),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(`50`),[d,f]=(0,g.useState)([]),[p,m]=(0,g.useState)({}),[h,_]=(0,g.useState)(!1),[v,y]=(0,g.useState)(``),[b,x]=(0,g.useState)(!1),[S,C]=(0,g.useState)(``);async function w(e=!1){x(!0),C(``);let n=new URLSearchParams({limit:l});t!==`all`&&n.set(`status`,t),r!==`all`&&n.set(`target_type`,r),a.trim()&&n.set(`reviewer`,a.trim()),s.trim()&&n.set(`q`,s.trim().replace(/^@/,``)),e&&v&&n.set(`before_id`,v);try{let t=await k.verificationApplications(n),r=t.rows??[];f(t=>e?[...t,...r]:r),y(t.next_before_id??``),_(!!t.has_more)}catch(e){C(O(e))}finally{x(!1)}}async function T(){try{m((await k.verificationCounts()).counts??{})}catch(e){C(O(e))}}(0,g.useEffect)(()=>{w(!1),T()},[]);function E(){w(!1),T()}return(0,W.jsxs)(G,{title:`Verification queue`,eyebrow:`Verification / Queue`,actions:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:E,disabled:b,children:[(0,W.jsx)(B,{size:15,className:b?`spin`:``}),` `,`Refresh`]}),children:[S&&(0,W.jsx)(q,{children:S}),(0,W.jsx)(`div`,{className:`metric-row`,children:Wi.map(e=>(0,W.jsx)(Y,{label:Ki[e],value:p[e]??`0`,mono:!0,tone:Zi(e,p[e]??`0`)},e))}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),w(!1)},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:s,onChange:e=>c(e.target.value),placeholder:`Application id, peer id, username or title`})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Status`}),(0,W.jsxs)(`select`,{value:t,onChange:e=>n(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All statuses`}),Wi.map(e=>(0,W.jsx)(`option`,{value:e,children:Ki[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Target type`}),(0,W.jsxs)(`select`,{value:r,onChange:e=>i(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All types`}),Gi.map(e=>(0,W.jsx)(`option`,{value:e,children:qi[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Reviewer`}),(0,W.jsx)(`input`,{value:a,onChange:e=>o(e.target.value),placeholder:`Any reviewer`})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsx)(`input`,{className:`small-input`,value:l,onChange:e=>u(e.target.value),type:`number`,min:`1`,max:`200`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:b,children:[b?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Target`}),(0,W.jsx)(`th`,{children:`Applicant`}),(0,W.jsx)(`th`,{children:`Category`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Submitted`}),(0,W.jsx)(`th`,{children:`Reviewer`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[d.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:(0,W.jsxs)(`button`,{className:`row-link`,type:`button`,onClick:()=>e(`/verification/${t.ID}`),children:[`#`,t.ID]})}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:Qi(t)}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[qi[t.TargetType],` · `,t.TargetID]}),t.TargetVerified&&(0,W.jsxs)(J,{tone:`good`,children:[(0,W.jsx)(ee,{size:12}),` `,`Badge already on`]})]}),(0,W.jsxs)(`td`,{children:[H(t.ApplicantUsername)||t.ApplicantName||`-`,(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:t.ApplicantUserID})]}),(0,W.jsx)(`td`,{children:t.Category||`-`}),(0,W.jsx)(`td`,{children:(0,W.jsx)(Yi,{status:t.Status})}),(0,W.jsx)(`td`,{children:U(t.SubmittedAt)||`-`}),(0,W.jsx)(`td`,{children:t.ReviewerAdminID||`-`}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,type:`button`,onClick:()=>e(`/verification/${t.ID}`),children:[(0,W.jsx)(ot,{size:14}),` `,`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),d.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})}),h&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>w(!0),disabled:b,children:[b?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})})]})}function Yi({status:e}){return(0,W.jsx)(J,{tone:Xi(e),children:Ki[e]})}function Xi(e){return e===`approved`?`good`:e===`submitted`||e===`in_review`?`warn`:e===`rejected`?`danger`:`neutral`}function Zi(e,t){return e===`submitted`||e===`in_review`?t!==`0`&&t!==``?`warn`:`neutral`:e===`approved`?`good`:`neutral`}function Qi(e){return H(e.TargetUsername)||e.TargetTitle||`#${e.TargetID}`}function $i(e){return e.TargetType===`bot`?`/bots/${e.TargetID}`:e.TargetType===`user`?`/accounts/${e.TargetID}`:`/channels/${e.TargetID}`}var ea={created:`Created`,updated:`Updated`,submitted:`Submitted`,claimed:`Claimed`,approved:`Approved`,rejected:`Rejected`,cancelled:`Cancelled`,revoked:`Badge revoked`,notified:`Applicant notified`};function ta({id:e,navigate:t}){let{can:n}=Xt(),[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(!1),[d,f]=(0,g.useState)(``);async function p(){u(!0),f(``);try{i(await k.verificationApplication(e))}catch(e){f(O(e))}finally{u(!1)}}function m(){c(!1),p()}(0,g.useEffect)(()=>{p()},[e]);function h(e){if(e instanceof v&&e.status===409)return c(!0),p(),`Another admin has already changed this application. The data has been reloaded — check the status before deciding again.`}if(d&&!r)return(0,W.jsx)(q,{children:d});if(!r)return(0,W.jsx)(Vt,{label:`Loading the application…`});let _=r.application,y=r.events??[],b=r.applicant_controls_target,x=r.target_verified,S=_.Status===`submitted`,C=_.Status===`submitted`||_.Status===`in_review`,w=_.Status===`approved`&&n(`verification.revoke`),T=a.trim();function E(){let e={version:_.Version};return T&&(e.internal_note=T),e}function D(){o(``),c(!1),p()}return(0,W.jsxs)(G,{title:`Application #${_.ID}`,eyebrow:`Verification / Review`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/verification`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to list`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:m,disabled:l,children:[(0,W.jsx)(B,{size:15,className:l?`spin`:``}),` `,`Refresh`]})]}),children:[d&&(0,W.jsx)(q,{children:d}),s&&(0,W.jsx)(q,{children:`Another admin has already changed this application. The data has been reloaded — check the status before deciding again.`}),(0,W.jsx)(Rt,{main:(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:Qi(_)}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[`#`,_.ID,` · `,qi[_.TargetType],`:`,_.TargetID,` · v`,_.Version]})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[(0,W.jsx)(Yi,{status:_.Status}),x&&(0,W.jsxs)(J,{tone:`good`,children:[(0,W.jsx)(ee,{size:12}),` `,`Badge already on`]}),(0,W.jsx)(J,{tone:b?`good`:`danger`,children:b?`Control confirmed`:`No control over the target`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Target`,text:`The peer the badge would be attached to, as it exists right now.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t($i(_)),children:[(0,W.jsx)(De,{size:15}),` `,`Open target`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Type`,value:qi[_.TargetType]}),(0,W.jsx)(X,{label:`Username`,value:H(_.TargetUsername)||`-`}),(0,W.jsx)(X,{label:`Title`,value:_.TargetTitle||`-`}),(0,W.jsx)(X,{label:`Peer ID`,value:_.TargetID,mono:!0})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Applicant`,text:`Who filed the application and whether they still hold rights on the target.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/accounts/${_.ApplicantUserID}`),children:[(0,W.jsx)(gt,{size:15}),` `,`Open account`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Username`,value:H(_.ApplicantUsername)||`-`}),(0,W.jsx)(X,{label:`Name`,value:_.ApplicantName||`-`}),(0,W.jsx)(X,{label:`User ID`,value:_.ApplicantUserID,mono:!0}),(0,W.jsx)(X,{label:`Submitted`,value:U(_.SubmittedAt)||`-`})]}),b?(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The applicant controls the target right now — checked against the live records, not against the submission snapshot.`}):(0,W.jsx)(q,{children:`The applicant no longer controls the target. Approving would hand the badge to someone who does not hold the peer — normally a reason to reject.`})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Application`,text:`Everything the applicant submitted, rendered as plain text.`}),(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Category`,value:_.Category||`-`}),(0,W.jsx)(X,{label:`Correlation ID`,value:_.CorrelationID||`-`,mono:!0}),(0,W.jsx)(X,{label:`Created`,value:U(_.CreatedAt)||`-`}),(0,W.jsx)(X,{label:`Updated`,value:U(_.UpdatedAt)||`-`})]}),(0,W.jsx)(na,{label:`Description`,children:_.Description?(0,W.jsx)(`p`,{className:`about-text`,children:_.Description}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`})}),(0,W.jsx)(na,{label:`Official website`,children:_.OfficialWebsite?(0,W.jsx)(`div`,{className:`about-text`,children:(0,W.jsx)(ra,{value:_.OfficialWebsite})}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`})}),(0,W.jsx)(na,{label:`Social links`,children:(0,W.jsx)(ia,{values:_.SocialLinks})}),(0,W.jsx)(na,{label:`Press coverage`,children:(0,W.jsx)(ia,{values:_.PressLinks})}),(0,W.jsx)(na,{label:`Applicant comment`,children:_.AdditionalNote?(0,W.jsx)(`p`,{className:`about-text`,children:_.AdditionalNote}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Only http:// and https:// links are clickable and open in a new tab; anything else is shown as text.`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Decision`,text:`What was decided, by whom, and with which wording.`}),(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Reviewer`,value:_.ReviewerAdminID||`-`}),(0,W.jsx)(X,{label:`Decided`,value:U(_.ReviewedAt)||`-`}),(0,W.jsx)(X,{label:`Status`,value:Ki[_.Status]}),(0,W.jsx)(X,{label:`Version (optimistic lock)`,value:_.Version,mono:!0})]}),(0,W.jsx)(na,{label:`Decision reason`,children:_.DecisionReason?(0,W.jsx)(`p`,{className:`about-text`,children:_.DecisionReason}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`No decision yet`})}),(0,W.jsx)(na,{label:`Internal note · admins only`,children:_.InternalNote?(0,W.jsx)(`p`,{className:`about-text`,children:_.InternalNote}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`})})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`History`,text:`Immutable trail of every status transition, with actor and reason.`}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Event`}),(0,W.jsx)(`th`,{children:`From → to`}),(0,W.jsx)(`th`,{children:`Actor`}),(0,W.jsx)(`th`,{children:`Reason`}),(0,W.jsx)(`th`,{children:`Internal note`}),(0,W.jsx)(`th`,{children:`Time`})]})}),(0,W.jsxs)(`tbody`,{children:[y.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{children:(0,W.jsx)(aa,{kind:e.Kind})}),(0,W.jsxs)(`td`,{className:`mono`,children:[e.FromStatus||`-`,` → `,e.ToStatus||`-`]}),(0,W.jsx)(`td`,{children:e.Actor||`-`}),(0,W.jsx)(`td`,{className:`truncate`,children:e.Reason||`-`}),(0,W.jsx)(`td`,{className:`truncate`,children:e.Note||`-`}),(0,W.jsx)(`td`,{children:U(e.CreatedAt)||`-`})]},e.ID)),y.length===0&&(0,W.jsx)(Bt,{colSpan:6})]})]})})]})]}),side:(0,W.jsxs)(`section`,{className:`action-dock`,children:[(0,W.jsx)(`div`,{className:`dock-title`,children:`Review actions`}),!S&&!C&&!w&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This status has no available actions.`}),S&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`div`,{className:`action-stack`,children:(0,W.jsx)(Z,{label:`Take into review`,icon:(0,W.jsx)(Ne,{size:15}),tone:`neutral`,path:`/api/verification/applications/${_.ID}/claim`,payload:()=>({version:_.Version}),onDone:D,onError:h})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Assigns the application to you and moves it to in review, so two reviewers never work on the same one.`})]}),(C||w)&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Internal note`}),(0,W.jsx)(`textarea`,{value:a,onChange:e=>o(e.target.value),rows:3,placeholder:`Handover note for other reviewers`})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Optional. Stored with the decision and visible to admins only — never sent to the applicant.`})]}),C&&(0,W.jsxs)(W.Fragment,{children:[!b&&(0,W.jsx)(q,{children:`The applicant no longer controls the target. Approving would hand the badge to someone who does not hold the peer — normally a reason to reject.`}),x&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The target already carries the badge; approving only records the decision.`}),(0,W.jsxs)(`div`,{className:`action-stack`,children:[(0,W.jsx)(Z,{label:`Approve`,icon:(0,W.jsx)(F,{size:15}),tone:`neutral`,path:`/api/verification/applications/${_.ID}/approve`,payload:E,onDone:D,onError:h}),(0,W.jsx)(Z,{label:`Reject`,icon:(0,W.jsx)(ne,{size:15}),tone:`warn`,path:`/api/verification/applications/${_.ID}/reject`,payload:E,onDone:D,onError:h})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Grants the official badge to the target and closes the application.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The reason is mandatory: it is the wording the applicant is told, so write what exactly was missing.`})]}),w&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`div`,{className:`dock-title`,children:[(0,W.jsx)(st,{size:14}),` `,`Danger zone`]}),(0,W.jsxs)(`div`,{className:`danger-zone`,children:[(0,W.jsx)(Z,{label:`Revoke verification`,icon:(0,W.jsx)(he,{size:15}),tone:`danger`,path:`/api/actions/revoke-verification`,payload:()=>{let e={target_type:_.TargetType,target_id:_.TargetID};return T&&(e.internal_note=T),e},onDone:D,onError:h}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Clears the badge from the target. The approved application stays in history.`}),!x&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The target carries no badge right now — there is nothing to revoke.`})]})]})]})})]})}function na({label:e,children:t}){return(0,W.jsxs)(`div`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:e}),t]})}function ra({value:e}){let t=Tt(e);return t?(0,W.jsxs)(`a`,{className:`row-link`,href:t,target:`_blank`,rel:`noopener noreferrer`,children:[e,` `,(0,W.jsx)(De,{size:13})]}):(0,W.jsx)(`span`,{className:`mono`,children:e})}function ia({values:e}){let t=(e??[]).filter(e=>e.trim()!==``);return t.length===0?(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`}):(0,W.jsx)(`div`,{className:`about-text`,children:t.map((e,t)=>(0,W.jsx)(`div`,{children:(0,W.jsx)(ra,{value:e})},`${t}-${e}`))})}function aa({kind:e}){return(0,W.jsx)(J,{tone:e===`approved`?`good`:e===`rejected`||e===`revoked`||e===`cancelled`?`danger`:e===`submitted`||e===`claimed`?`warn`:`neutral`,children:ea[e]})}function oa({route:e,navigate:t}){let n=e.path.match(/^\/accounts\/(\d+)$/)?.[1],r=e.path.match(/^\/channels\/(\d+)$/)?.[1],i=e.path.match(/^\/bots\/(\d+)$/)?.[1],a=e.path.match(/^\/moderation\/(\d+)$/)?.[1],o=e.path.match(/^\/collectible-usernames\/(\d+)$/)?.[1],s=e.path.match(/^\/verification\/(\d+)$/)?.[1],c=e.path.match(/^\/bot-verification\/(\d+)$/)?.[1];return c?(0,W.jsx)(tn,{children:(0,W.jsx)($t,{permission:Gt,children:(0,W.jsx)(Vi,{id:c,navigate:t})})}):e.path===`/bot-verification`?(0,W.jsx)(tn,{children:(0,W.jsx)($t,{permission:Gt,children:(0,W.jsx)(Ai,{navigate:t})})}):s?(0,W.jsx)($t,{permission:Wt,children:(0,W.jsx)(ta,{id:s,navigate:t})}):e.path===`/verification`?(0,W.jsx)($t,{permission:Wt,children:(0,W.jsx)(Ji,{navigate:t})}):o?(0,W.jsx)(er,{id:o,navigate:t}):e.path===`/collectible-usernames`?(0,W.jsx)(Xn,{navigate:t}):e.path===`/storage`?(0,W.jsx)(di,{navigate:t}):n?(0,W.jsx)(Rn,{id:Number(n),navigate:t}):r?(0,W.jsx)(ir,{id:Number(r),navigate:t}):i?(0,W.jsx)(cr,{id:Number(i),navigate:t}):a?(0,W.jsx)(ii,{id:Number(a),navigate:t}):e.path===`/accounts/shared-devices`?(0,W.jsx)(Wn,{navigate:t}):e.path===`/accounts`?(0,W.jsx)(Un,{navigate:t}):e.path===`/channels`?(0,W.jsx)(or,{navigate:t}):e.path===`/bots`?(0,W.jsx)(dr,{navigate:t}):e.path===`/moderation`?(0,W.jsx)(Zr,{navigate:t}):e.path===`/broadcasts`?(0,W.jsx)(mr,{}):e.path===`/emoji`?(0,W.jsx)(Or,{kind:`emoji`}):e.path===`/stickers`?(0,W.jsx)(Or,{kind:`stickers`}):e.path===`/gif-catalog`?(0,W.jsx)(jr,{}):e.path===`/server-settings`?(0,W.jsx)($t,{permission:qt,children:(0,W.jsx)(Nr,{})}):e.path===`/messages/detail`||e.path===`/messages/private/detail`?(0,W.jsx)(br,{ownerUserID:Number(e.search.get(`owner_user_id`)||`0`),msgID:Number(e.search.get(`msg_id`)||`0`),navigate:t}):e.path===`/messages/groups/detail`?(0,W.jsx)(Q,{channelID:Number(e.search.get(`channel_id`)||`0`),msgID:Number(e.search.get(`msg_id`)||`0`),navigate:t}):e.path===`/messages/groups`?(0,W.jsx)(yr,{navigate:t}):e.path===`/messages`||e.path===`/messages/private`?(0,W.jsx)(xr,{navigate:t}):(0,W.jsx)(hr,{navigate:t})}function sa(){let[e,t]=(0,g.useState)(void 0),[n,r]=(0,g.useState)(()=>nn());(0,g.useEffect)(()=>{let e=()=>r(nn());return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},[]),(0,g.useEffect)(()=>{k.session().then(e=>t(e)).catch(()=>t(null))},[]);let i=e=>{window.history.pushState(null,``,e),r(nn())};return e===void 0?(0,W.jsx)(_n,{}):e===null?(0,W.jsx)(bn,{onLogin:t}):(0,W.jsx)(Yt,{permissions:e.permissions??[],hideThirdPartyVerification:e.hide_third_party_verification??!0,children:(0,W.jsx)(vn,{actor:e.actor,apiLayers:e.api_layers,build:e.build,route:n,navigate:i,onLogout:()=>t(null),children:(0,W.jsx)(oa,{route:n,navigate:i})})})}_.createRoot(document.getElementById(`root`)).render((0,W.jsx)(g.StrictMode,{children:(0,W.jsx)(cn,{children:(0,W.jsx)(sa,{})})})); \ No newline at end of file +`),n={},r,i=0,a=0;athis.animationData.op&&(this.animationData.op=e.op,this.totalFrames=Math.floor(e.op-this.animationData.ip));var t=this.animationData.layers,n,r=t.length,i=e.layers,a,o=i.length;for(a=0;athis.timeCompleted&&(this.currentFrame=this.timeCompleted),this.trigger(`enterFrame`),this.renderFrame(),this.trigger(`drawnFrame`)},R.prototype.renderFrame=function(){if(!(this.isLoaded===!1||!this.renderer))try{this.expressionsPlugin&&this.expressionsPlugin.resetFrame(),this.renderer.renderFrame(this.currentFrame+this.firstFrame)}catch(e){this.triggerRenderFrameError(e)}},R.prototype.play=function(e){e&&this.name!==e||this.isPaused===!0&&(this.isPaused=!1,this.trigger(`_play`),this.audioController.resume(),this._idle&&(this._idle=!1,this.trigger(`_active`)))},R.prototype.pause=function(e){e&&this.name!==e||this.isPaused===!1&&(this.isPaused=!0,this.trigger(`_pause`),this._idle=!0,this.trigger(`_idle`),this.audioController.pause())},R.prototype.togglePause=function(e){e&&this.name!==e||(this.isPaused===!0?this.play():this.pause())},R.prototype.stop=function(e){e&&this.name!==e||(this.pause(),this.playCount=0,this._completedLoop=!1,this.setCurrentRawFrameValue(0))},R.prototype.getMarkerData=function(e){for(var t,n=0;n=this.totalFrames-1&&this.frameModifier>0?!this.loop||this.playCount===this.loop?this.checkSegments(t>this.totalFrames?t%this.totalFrames:0)||(n=!0,t=this.totalFrames-1):t>=this.totalFrames?(this.playCount+=1,this.checkSegments(t%this.totalFrames)||(this.setCurrentRawFrameValue(t%this.totalFrames),this._completedLoop=!0,this.trigger(`loopComplete`))):this.setCurrentRawFrameValue(t):t<0?this.checkSegments(t%this.totalFrames)||(this.loop&&!(this.playCount--<=0&&this.loop!==!0)?(this.setCurrentRawFrameValue(this.totalFrames+t%this.totalFrames),this._completedLoop?this.trigger(`loopComplete`):this._completedLoop=!0):(n=!0,t=0)):this.setCurrentRawFrameValue(t),n&&(this.setCurrentRawFrameValue(t),this.pause(),this.trigger(`complete`))}},R.prototype.adjustSegment=function(e,t){this.playCount=0,e[1]0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(-1)),this.totalFrames=e[0]-e[1],this.timeCompleted=this.totalFrames,this.firstFrame=e[1],this.setCurrentRawFrameValue(this.totalFrames-.001-t)):e[1]>e[0]&&(this.frameModifier<0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(1)),this.totalFrames=e[1]-e[0],this.timeCompleted=this.totalFrames,this.firstFrame=e[0],this.setCurrentRawFrameValue(.001+t)),this.trigger(`segmentStart`)},R.prototype.setSegment=function(e,t){var n=-1;this.isPaused&&(this.currentRawFrame+this.firstFramet&&(n=t-e)),this.firstFrame=e,this.totalFrames=t-e,this.timeCompleted=this.totalFrames,n!==-1&&this.goToAndStop(n,!0)},R.prototype.playSegments=function(e,t){if(t&&(this.segments.length=0),Ce(e[0])===`object`){var n,r=e.length;for(n=0;n=0;--n)t[n].animation.destroy(e)}function T(e,t,n){var r=[].concat([].slice.call(document.getElementsByClassName(`lottie`)),[].slice.call(document.getElementsByClassName(`bodymovin`))),i,a=r.length;for(i=0;i0?n=c:t=c;while(Math.abs(s)>a&&++l=i?g(e,d,t,n):f===0?d:h(e,a,a+c,t,n)}},e}(),Ee=function(){function e(e){return e.concat(m(e.length))}return{double:e}}(),De=function(){return function(e,t,n){var r=0,i=e,a=m(i),o={newElement:s,release:c};function s(){var e;return r?(--r,e=a[r]):e=t(),e}function c(e){r===i&&(a=Ee.double(a),i*=2),n&&n(e),a[r]=e,r+=1}return o}}(),Oe=function(){function e(){return{addedLength:0,percents:p(`float32`,de()),lengths:p(`float32`,de())}}return De(8,e)}(),ke=function(){function e(){return{lengths:[],totalLength:0}}function t(e){var t,n=e.lengths.length;for(t=0;t-.001&&o<.001}function n(n,r,i,a,o,s,c,l,u){if(i===0&&s===0&&u===0)return t(n,r,a,o,c,l);var d=e.sqrt(e.pow(a-n,2)+e.pow(o-r,2)+e.pow(s-i,2)),f=e.sqrt(e.pow(c-n,2)+e.pow(l-r,2)+e.pow(u-i,2)),p=e.sqrt(e.pow(c-a,2)+e.pow(l-o,2)+e.pow(u-s,2)),m=d>f?d>p?d-f-p:p-f-d:p>f?p-f-d:f-d-p;return m>-1e-4&&m<1e-4}var r=function(){return function(e,t,n,r){var i=de(),a,o,s,c,l,u=0,d,f=[],p=[],m=Oe.newElement();for(s=n.length,a=0;ao?-1:1,l=!0;l;)if(r[a]<=o&&r[a+1]>o?(s=(o-r[a])/(r[a+1]-r[a]),l=!1):a+=c,a<0||a>=i-1){if(a===i-1)return n[a];l=!1}return n[a]+(n[a+1]-n[a])*s}function l(t,n,r,i,a,o){var s=c(a,o),l=1-s;return[e.round((l*l*l*t[0]+(s*l*l+l*s*l+l*l*s)*r[0]+(s*s*l+l*s*s+s*l*s)*i[0]+s*s*s*n[0])*1e3)/1e3,e.round((l*l*l*t[1]+(s*l*l+l*s*l+l*l*s)*r[1]+(s*s*l+l*s*s+s*l*s)*i[1]+s*s*s*n[1])*1e3)/1e3]}var u=p(`float32`,8);function d(t,n,r,i,a,o,s){a<0?a=0:a>1&&(a=1);var l=c(a,s);o=o>1?1:o;var d=c(o,s),f,p=t.length,m=1-l,h=1-d,g=m*m*m,_=l*m*m*3,v=l*l*m*3,y=l*l*l,b=m*m*h,x=l*m*h+m*l*h+m*m*d,S=l*l*h+m*l*d+l*m*d,C=l*l*d,w=m*h*h,T=l*h*h+m*d*h+m*h*d,E=l*d*h+m*d*d+l*h*d,D=l*d*d,O=h*h*h,k=d*h*h+h*d*h+h*h*d,A=d*d*h+h*d*d+d*h*d,j=d*d*d;for(f=0;f=l.t-n){c.h&&(c=l),i=0;break}if(l.t-n>e){i=a;break}a=v||e=v?x.points.length-1:0;for(f=x.points[S].point.length,d=0;d=T&&C=v)r[0]=b[0],r[1]=b[1],r[2]=b[2];else if(e<=y)r[0]=c.s[0],r[1]=c.s[1],r[2]=c.s[2];else{var j=Le(c.s),M=Le(b),N=(e-y)/(v-y);Ie(r,Fe(j,M,N))}else for(a=0;a=v?m=1:e1e-6?(f=Math.acos(p),m=Math.sin(f),h=Math.sin((1-n)*f)/m,g=Math.sin(n*f)/m):(h=1-n,g=n),r[0]=h*i+g*c,r[1]=h*a+g*l,r[2]=h*o+g*u,r[3]=h*s+g*d,r}function Ie(e,t){var n=t[0],r=t[1],i=t[2],a=t[3],o=Math.atan2(2*r*a-2*n*i,1-2*r*r-2*i*i),s=Math.asin(2*n*r+2*i*a),c=Math.atan2(2*n*a-2*r*i,1-2*n*n-2*i*i);e[0]=o/D,e[1]=s/D,e[2]=c/D}function Le(e){var t=e[0]*D,n=e[1]*D,r=e[2]*D,i=Math.cos(t/2),a=Math.cos(n/2),o=Math.cos(r/2),s=Math.sin(t/2),c=Math.sin(n/2),l=Math.sin(r/2),u=i*a*o-s*c*l;return[s*c*o+i*a*l,s*a*o+i*c*l,i*c*o-s*a*l,u]}function Re(){var e=this.comp.renderedFrame-this.offsetTime,t=this.keyframes[0].t-this.offsetTime,n=this.keyframes[this.keyframes.length-1].t-this.offsetTime;if(!(e===this._caching.lastFrame||this._caching.lastFrame!==Me&&(this._caching.lastFrame>=n&&e>=n||this._caching.lastFrame=e&&(this._caching._lastKeyframeIndex=-1,this._caching.lastIndex=0);var r=this.interpolateValue(e,this._caching);this.pv=r}return this._caching.lastFrame=e,this.pv}function ze(e){var t;if(this.propType===`unidimensional`)t=e*this.mult,Ne(this.v-t)>1e-5&&(this.v=t,this._mdf=!0);else for(var n=0,r=this.v.length;n1e-5&&(this.v[n]=t,this._mdf=!0),n+=1}function Be(){if(!(this.elem.globalData.frameId===this.frameId||!this.effectsSequence.length)){if(this.lock){this.setVValue(this.pv);return}this.lock=!0,this._mdf=this._isFirstFrame;var e,t=this.effectsSequence.length,n=this.kf?this.pv:this.data.k;for(e=0;e=this._maxLength&&this.doubleArrayLength(),n){case`v`:a=this.v;break;case`i`:a=this.i;break;case`o`:a=this.o;break;default:a=[];break}(!a[r]||a[r]&&!i)&&(a[r]=qe.newElement()),a[r][0]=e,a[r][1]=t},Je.prototype.setTripleAt=function(e,t,n,r,i,a,o,s){this.setXYAt(e,t,`v`,o,s),this.setXYAt(n,r,`o`,o,s),this.setXYAt(i,a,`i`,o,s)},Je.prototype.reverse=function(){var e=new Je;e.setPathData(this.c,this._length);var t=this.v,n=this.o,r=this.i,i=0;this.c&&(e.setTripleAt(t[0][0],t[0][1],r[0][0],r[0][1],n[0][0],n[0][1],0,!1),i=1);var a=this._length-1,o=this._length,s;for(s=i;s=p[p.length-1].t-this.offsetTime)i=p[p.length-1].s?p[p.length-1].s[0]:p[p.length-2].e[0],o=!0;else{for(var m=r,h=p.length-1,g=!0,_,v,y;g&&(_=p[m],v=p[m+1],!(v.t-this.offsetTime>e));)m=v.t-this.offsetTime)d=1;else if(e<_.t-this.offsetTime)d=0;else{var b;y.__fnct?b=y.__fnct:(b=Te.getBezierEasing(_.o.x,_.o.y,_.i.x,_.i.y).get,y.__fnct=b),d=b((e-(_.t-this.offsetTime))/(v.t-this.offsetTime-(_.t-this.offsetTime)))}a=v.s?v.s[0]:_.e[0]}i=_.s[0]}for(l=t._length,u=i.i[0].length,n.lastIndex=r,s=0;sr&&t>r)||(this._caching.lastIndex=i0||e>-1e-6&&e<0?r(e*t)/t:e}function P(){var e=this.props,t=N(e[0]),n=N(e[1]),r=N(e[4]),i=N(e[5]),a=N(e[12]),o=N(e[13]);return`matrix(`+t+`,`+n+`,`+r+`,`+i+`,`+a+`,`+o+`)`}return function(){this.reset=i,this.rotate=a,this.rotateX=o,this.rotateY=s,this.rotateZ=c,this.skew=u,this.skewFromAxis=d,this.shear=l,this.scale=f,this.setTransform=m,this.translate=h,this.transform=g,this.multiply=_,this.applyToPoint=S,this.applyToX=C,this.applyToY=w,this.applyToZ=T,this.applyToPointArray=A,this.applyToTriplePoints=k,this.applyToPointStringified=j,this.toCSS=M,this.to2dCSS=P,this.clone=b,this.cloneFromProps=x,this.equals=y,this.inversePoints=O,this.inversePoint=D,this.getInverseMatrix=E,this._t=this.transform,this.isIdentity=v,this._identity=!0,this._identityCalculated=!1,this.props=p(`float32`,16),this.reset()}}();function $e(e){"@babel/helpers - typeof";return $e=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},$e(e)}var V={},et=`__[STANDALONE]__`,tt=`__[ANIMATIONDATA]__`,nt=``;function rt(e){s(e)}function it(){et===!0?we.searchAnimations(tt,et,nt):we.searchAnimations()}function at(e){ae(e)}function ot(e){fe(e)}function st(e){return et===!0&&(e.animationData=JSON.parse(tt)),we.loadAnimation(e)}function ct(e){if(typeof e==`string`)switch(e){case`high`:ue(200);break;default:case`medium`:ue(50);break;case`low`:ue(10);break}else!isNaN(e)&&e>1&&ue(e)}function lt(){return typeof navigator<`u`}function ut(e,t){e===`expressions`&&se(t)}function dt(e){switch(e){case`propertyFactory`:return z;case`shapePropertyFactory`:return Qe;case`matrix`:return B;default:return null}}V.play=we.play,V.pause=we.pause,V.setLocationHref=rt,V.togglePause=we.togglePause,V.setSpeed=we.setSpeed,V.setDirection=we.setDirection,V.stop=we.stop,V.searchAnimations=it,V.registerAnimation=we.registerAnimation,V.loadAnimation=st,V.setSubframeRendering=at,V.resize=we.resize,V.goToAndStop=we.goToAndStop,V.destroy=we.destroy,V.setQuality=ct,V.inBrowser=lt,V.installPlugin=ut,V.freeze=we.freeze,V.unfreeze=we.unfreeze,V.setVolume=we.setVolume,V.mute=we.mute,V.unmute=we.unmute,V.getRegisteredAnimations=we.getRegisteredAnimations,V.useWebWorker=a,V.setIDPrefix=ot,V.__getFactory=dt,V.version=`5.13.0`;function ft(){document.readyState===`complete`&&(clearInterval(_t),it())}function pt(e){for(var t=mt.split(`&`),n=0;n=1?a.push({s:e-1,e:t-1}):(a.push({s:e,e:1}),a.push({s:0,e:t-1}));var o=[],s,c=a.length,l;for(s=0;sr+n)){var u=l.s*i<=r?0:(l.s*i-r)/n,d=l.e*i>=r+n?1:(l.e*i-r)/n;o.push([u,d])}return o.length||o.push([0,0]),o},bt.prototype.releasePathsData=function(e){var t,n=e.length;for(t=0;t1?1+r:this.s.v<0?0+r:this.s.v+r,n=this.e.v>1?1+r:this.e.v<0?0+r:this.e.v+r,t>n){var i=t;t=n,n=i}t=Math.round(t*1e4)*1e-4,n=Math.round(n*1e4)*1e-4,this.sValue=t,this.eValue=n}else t=this.sValue,n=this.eValue;var a,o,s=this.shapes.length,c,l,u,d,f,p=0;if(n===t)for(o=0;o=0;--o)if(h=this.shapes[o],h.shape._mdf){for(g=h.localShapeCollection,g.releaseShapes(),this.m===2&&s>1?(b=this.calculateShapeEdges(t,n,h.totalShapeLength,y,p),y+=h.totalShapeLength):b=[[_,v]],l=b.length,c=0;c=1?m.push({s:h.totalShapeLength*(_-1),e:h.totalShapeLength*(v-1)}):(m.push({s:h.totalShapeLength*_,e:h.totalShapeLength}),m.push({s:0,e:h.totalShapeLength*(v-1)}));var x=this.addShapes(h,m[0]);if(m[0].s!==m[0].e){if(m.length>1)if(h.shape.paths.shapes[h.shape.paths._length-1].c){var S=x.pop();this.addPaths(x,g),x=this.addShapes(h,m[1],S)}else this.addPaths(x,g),x=this.addShapes(h,m[1]);this.addPaths(x,g)}}h.shape.paths=g}}else if(this._mdf)for(o=0;ot.e){n.c=!1;break}else t.s<=l&&t.e>=l+u.addedLength?(this.addSegment(i[a].v[s-1],i[a].o[s-1],i[a].i[s],i[a].v[s],n,d,g),g=!1):(p=je.getNewSegment(i[a].v[s-1],i[a].v[s],i[a].o[s-1],i[a].i[s],(t.s-l)/u.addedLength,(t.e-l)/u.addedLength,f[s-1]),this.addSegmentFromArray(p,n,d,g),g=!1,n.c=!1),l+=u.addedLength,d+=1;if(i[a].c&&f.length){if(u=f[s-1],l<=t.e){var _=f[s-1].addedLength;t.s<=l&&t.e>=l+_?(this.addSegment(i[a].v[s-1],i[a].o[s-1],i[a].i[0],i[a].v[0],n,d,g),g=!1):(p=je.getNewSegment(i[a].v[s-1],i[a].v[0],i[a].o[s-1],i[a].i[0],(t.s-l)/_,(t.e-l)/_,f[s-1]),this.addSegmentFromArray(p,n,d,g),g=!1,n.c=!1)}else n.c=!1;l+=u.addedLength,d+=1}if(n._length&&(n.setXYAt(n.v[h][0],n.v[h][1],`i`,h),n.setXYAt(n.v[n._length-1][0],n.v[n._length-1][1],`o`,n._length-1)),l>t.e)break;a=this.p.keyframes[this.p.keyframes.length-1].t?(r=this.p.getValueAtTime(this.p.keyframes[this.p.keyframes.length-1].t/n,0),i=this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length-1].t-.05)/n,0)):(r=this.p.pv,i=this.p.getValueAtTime((this.p._caching.lastFrame+this.p.offsetTime-.01)/n,this.p.offsetTime));else if(this.px&&this.px.keyframes&&this.py.keyframes&&this.px.getValueAtTime&&this.py.getValueAtTime){r=[],i=[];var a=this.px,o=this.py;a._caching.lastFrame+a.offsetTime<=a.keyframes[0].t?(r[0]=a.getValueAtTime((a.keyframes[0].t+.01)/n,0),r[1]=o.getValueAtTime((o.keyframes[0].t+.01)/n,0),i[0]=a.getValueAtTime(a.keyframes[0].t/n,0),i[1]=o.getValueAtTime(o.keyframes[0].t/n,0)):a._caching.lastFrame+a.offsetTime>=a.keyframes[a.keyframes.length-1].t?(r[0]=a.getValueAtTime(a.keyframes[a.keyframes.length-1].t/n,0),r[1]=o.getValueAtTime(o.keyframes[o.keyframes.length-1].t/n,0),i[0]=a.getValueAtTime((a.keyframes[a.keyframes.length-1].t-.01)/n,0),i[1]=o.getValueAtTime((o.keyframes[o.keyframes.length-1].t-.01)/n,0)):(r=[a.pv,o.pv],i[0]=a.getValueAtTime((a._caching.lastFrame+a.offsetTime-.01)/n,a.offsetTime),i[1]=o.getValueAtTime((o._caching.lastFrame+o.offsetTime-.01)/n,o.offsetTime))}else i=e,r=i;this.v.rotate(-Math.atan2(r[1]-i[1],r[0]-i[0]))}this.data.p&&this.data.p.s?this.data.p.z?this.v.translate(this.px.v,this.py.v,-this.pz.v):this.v.translate(this.px.v,this.py.v,0):this.v.translate(this.p.v[0],this.p.v[1],-this.p.v[2])}this.frameId=this.elem.globalData.frameId}}function r(){if(this.appliedTransformations=0,this.pre.reset(),!this.a.effectsSequence.length)this.pre.translate(-this.a.v[0],-this.a.v[1],this.a.v[2]),this.appliedTransformations=1;else return;if(!this.s.effectsSequence.length)this.pre.scale(this.s.v[0],this.s.v[1],this.s.v[2]),this.appliedTransformations=2;else return;if(this.sk)if(!this.sk.effectsSequence.length&&!this.sa.effectsSequence.length)this.pre.skewFromAxis(-this.sk.v,this.sa.v),this.appliedTransformations=3;else return;this.r?this.r.effectsSequence.length||(this.pre.rotate(-this.r.v),this.appliedTransformations=4):!this.rz.effectsSequence.length&&!this.ry.effectsSequence.length&&!this.rx.effectsSequence.length&&!this.or.effectsSequence.length&&(this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]),this.appliedTransformations=4)}function i(){}function a(e){this._addDynamicProperty(e),this.elem.addDynamicProperty(e),this._isDirty=!0}function o(e,t,n){if(this.elem=e,this.frameId=-1,this.propType=`transform`,this.data=t,this.v=new B,this.pre=new B,this.appliedTransformations=0,this.initDynamicPropertyContainer(n||e),t.p&&t.p.s?(this.px=z.getProp(e,t.p.x,0,0,this),this.py=z.getProp(e,t.p.y,0,0,this),t.p.z&&(this.pz=z.getProp(e,t.p.z,0,0,this))):this.p=z.getProp(e,t.p||{k:[0,0,0]},1,0,this),t.rx){if(this.rx=z.getProp(e,t.rx,0,D,this),this.ry=z.getProp(e,t.ry,0,D,this),this.rz=z.getProp(e,t.rz,0,D,this),t.or.k[0].ti){var r,i=t.or.k.length;for(r=0;r0;)--n,this._elements.unshift(t[n]);this.dynamicProperties.length?this.k=!0:this.getValue(!0)},St.prototype.resetElements=function(e){var t,n=e.length;for(t=0;t0?Math.floor(f):Math.ceil(f),h=this.pMatrix.props,g=this.rMatrix.props,_=this.sMatrix.props;this.pMatrix.reset(),this.rMatrix.reset(),this.sMatrix.reset(),this.tMatrix.reset(),this.matrix.reset();var v=0;if(f>0){for(;vm;)this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!0),--v;p&&(this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,-p,!0),v-=p)}r=this.data.m===1?0:this._currentCopies-1,i=this.data.m===1?1:-1,a=this._currentCopies;for(var y,b;a;){if(t=this.elemsData[r].it,n=t[t.length-1].transform.mProps.v.props,b=n.length,t[t.length-1].transform.mProps._mdf=!0,t[t.length-1].transform.op._mdf=!0,t[t.length-1].transform.op.v=this._currentCopies===1?this.so.v:this.so.v+(this.eo.v-this.so.v)*(r/(this._currentCopies-1)),v!==0){for((r!==0&&i===1||r!==this._currentCopies-1&&i===-1)&&this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!1),this.matrix.transform(g[0],g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15]),this.matrix.transform(_[0],_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15]),this.matrix.transform(h[0],h[1],h[2],h[3],h[4],h[5],h[6],h[7],h[8],h[9],h[10],h[11],h[12],h[13],h[14],h[15]),y=0;y0&&r<1?[t]:[]:[t-r,t+r].filter(function(e){return e>0&&e<1})},At.prototype.split=function(e){if(e<=0)return[kt(this.points[0]),this];if(e>=1)return[this,kt(this.points[this.points.length-1])];var t=Et(this.points[0],this.points[1],e),n=Et(this.points[1],this.points[2],e),r=Et(this.points[2],this.points[3],e),i=Et(t,n,e),a=Et(n,r,e),o=Et(i,a,e);return[new At(this.points[0],t,i,o,!0),new At(o,a,r,this.points[3],!0)]};function jt(e,t){var n=e.points[0][t],r=e.points[e.points.length-1][t];if(n>r){var i=r;r=n,n=i}for(var a=Dt(3*e.a[t],2*e.b[t],e.c[t]),o=0;o0&&a[o]<1){var s=e.point(a[o])[t];sr&&(r=s)}return{min:n,max:r}}At.prototype.bounds=function(){return{x:jt(this,0),y:jt(this,1)}},At.prototype.boundingBox=function(){var e=this.bounds();return{left:e.x.min,right:e.x.max,top:e.y.min,bottom:e.y.max,width:e.x.max-e.x.min,height:e.y.max-e.y.min,cx:(e.x.max+e.x.min)/2,cy:(e.y.max+e.y.min)/2}};function Mt(e,t,n){var r=e.boundingBox();return{cx:r.cx,cy:r.cy,width:r.width,height:r.height,bez:e,t:(t+n)/2,t1:t,t2:n}}function Nt(e){var t=e.bez.split(.5);return[Mt(t[0],e.t1,e.t),Mt(t[1],e.t,e.t2)]}function Pt(e,t){return Math.abs(e.cx-t.cx)*2=a||e.width<=r&&e.height<=r&&t.width<=r&&t.height<=r){i.push([e.t,t.t]);return}var o=Nt(e),s=Nt(t);Ft(o[0],s[0],n+1,r,i,a),Ft(o[0],s[1],n+1,r,i,a),Ft(o[1],s[0],n+1,r,i,a),Ft(o[1],s[1],n+1,r,i,a)}}At.prototype.intersections=function(e,t,n){t===void 0&&(t=2),n===void 0&&(n=7);var r=[];return Ft(Mt(this,0,1),Mt(e,0,1),0,t,r,n),r},At.shapeSegment=function(e,t){var n=(t+1)%e.length();return new At(e.v[t],e.o[t],e.i[n],e.v[n],!0)},At.shapeSegmentInverted=function(e,t){var n=(t+1)%e.length();return new At(e.v[n],e.i[n],e.o[t],e.v[t],!0)};function It(e,t){return[e[1]*t[2]-e[2]*t[1],e[2]*t[0]-e[0]*t[2],e[0]*t[1]-e[1]*t[0]]}function W(e,t,n,r){var i=[e[0],e[1],1],a=[t[0],t[1],1],o=[n[0],n[1],1],s=[r[0],r[1],1],c=It(It(i,a),It(o,s));return wt(c[2])?null:[c[0]/c[2],c[1]/c[2]]}function G(e,t,n){return[e[0]+Math.cos(t)*n,e[1]-Math.sin(t)*n]}function Lt(e,t){return Math.hypot(e[0]-t[0],e[1]-t[1])}function Rt(e,t){return U(e[0],t[0])&&U(e[1],t[1])}function K(){}u([yt],K),K.prototype.initModifierProperties=function(e,t){this.getValue=this.processKeys,this.amplitude=z.getProp(e,t.s,0,null,this),this.frequency=z.getProp(e,t.r,0,null,this),this.pointsType=z.getProp(e,t.pt,0,null,this),this._isAnimated=this.amplitude.effectsSequence.length!==0||this.frequency.effectsSequence.length!==0||this.pointsType.effectsSequence.length!==0};function q(e,t,n,r,i,a,o){var s=n-Math.PI/2,c=n+Math.PI/2,l=t[0]+Math.cos(n)*r*i,u=t[1]-Math.sin(n)*r*i;e.setTripleAt(l,u,l+Math.cos(s)*a,u-Math.sin(s)*a,l+Math.cos(c)*o,u-Math.sin(c)*o,e.length())}function J(e,t){var n=[t[0]-e[0],t[1]-e[1]],r=-Math.PI*.5;return[Math.cos(r)*n[0]-Math.sin(r)*n[1],Math.sin(r)*n[0]+Math.cos(r)*n[1]]}function Y(e,t){var n=t===0?e.length()-1:t-1,r=(t+1)%e.length(),i=e.v[n],a=e.v[r],o=J(i,a);return Math.atan2(0,1)-Math.atan2(o[1],o[0])}function X(e,t,n,r,i,a,o){var s=Y(t,n),c=t.v[n%t._length],l=t.v[n===0?t._length-1:n-1],u=t.v[(n+1)%t._length],d=a===2?Math.sqrt((c[0]-l[0])**2+(c[1]-l[1])**2):0,f=a===2?Math.sqrt((c[0]-u[0])**2+(c[1]-u[1])**2):0;q(e,t.v[n%t._length],s,o,r,f/((i+1)*2),d/((i+1)*2),a)}function zt(e,t,n,r,i,a){for(var o=0;o1&&t.length>1&&(i=Ut(e[0],t[t.length-1]),i)?[[e[0].split(i[0])[0]],[t[t.length-1].split(i[1])[1]]]:[n,r]}function Gt(e){for(var t,n=1;n1&&(t=Wt(e[e.length-1],e[0]),e[e.length-1]=t[0],e[0]=t[1]),e}function Kt(e,t){var n=e.inflectionPoints(),r,i,a,o;if(n.length===0)return[Vt(e,t)];if(n.length===1||U(n[1],1))return a=e.split(n[0]),r=a[0],i=a[1],[Vt(r,t),Vt(i,t)];a=e.split(n[0]),r=a[0];var s=(n[1]-n[0])/(1-n[0]);return a=a[1].split(s),o=a[0],i=a[1],[Vt(r,t),Vt(o,t),Vt(i,t)]}function qt(){}u([yt],qt),qt.prototype.initModifierProperties=function(e,t){this.getValue=this.processKeys,this.amount=z.getProp(e,t.a,0,null,this),this.miterLimit=z.getProp(e,t.ml,0,null,this),this.lineJoin=t.lj,this._isAnimated=this.amount.effectsSequence.length!==0},qt.prototype.processPath=function(e,t,n,r){var i=Ye.newElement();i.c=e.c;var a=e.length();e.c||--a;var o,s,c,l=[];for(o=0;o=0;--o)c=At.shapeSegmentInverted(e,o),l.push(Kt(c,t));l=Gt(l);var u=null,d=null;for(o=0;o0&&(o=!1),o){var u=l(`style`);u.setAttribute(`f-forigin`,n[r].fOrigin),u.setAttribute(`f-origin`,n[r].origin),u.setAttribute(`f-family`,n[r].fFamily),u.type=`text/css`,u.innerText=`@font-face {font-family: `+n[r].fFamily+`; font-style: normal; src: url('`+n[r].fPath+`');}`,t.appendChild(u)}}else if(n[r].fOrigin===`g`||n[r].origin===1){for(s=document.querySelectorAll(`link[f-forigin="g"], link[f-origin="1"]`),c=0;c=55296&&n<=56319){var r=e.charCodeAt(1);r>=56320&&r<=57343&&(t=(n-55296)*1024+r-56320+65536)}return t}function S(e,t){var n=e.toString(16)+t.toString(16);return d.indexOf(n)!==-1}function C(e){return e===s}function w(e){return e===o}function T(e){var t=x(e);return t>=c&&t<=u}function E(e){return T(e.substr(0,2))&&T(e.substr(2,2))}function D(e){return t.indexOf(e)!==-1}function O(e,t){var o=x(e.substr(t,2));if(o!==n)return!1;var s=0;for(t+=2;s<5;){if(o=x(e.substr(t,2)),oa)return!1;s+=1,t+=2}return x(e.substr(t,2))===r}function k(){this.isLoaded=!0}var A=function(){this.fonts=[],this.chars=null,this.typekitLoaded=0,this.isLoaded=!1,this._warned=!1,this.initTime=Date.now(),this.setIsLoadedBinded=this.setIsLoaded.bind(this),this.checkLoadedFontsBinded=this.checkLoadedFonts.bind(this)};return A.isModifier=S,A.isZeroWidthJoiner=C,A.isFlagEmoji=E,A.isRegionalCode=T,A.isCombinedCharacter=D,A.isRegionalFlag=O,A.isVariationSelector=w,A.BLACK_FLAG_CODE_POINT=n,A.prototype={addChars:_,addFonts:g,getCharData:v,getFontByName:b,measureText:y,checkLoadedFonts:m,setIsLoaded:k},A}();function Xt(e){this.animationData=e}Xt.prototype.getProp=function(e){return this.animationData.slots&&this.animationData.slots[e.sid]?Object.assign(e,this.animationData.slots[e.sid].p):e};function Zt(e){return new Xt(e)}function Qt(){}Qt.prototype={initRenderable:function(){this.isInRange=!1,this.hidden=!1,this.isTransparent=!1,this.renderableComponents=[]},addRenderableComponent:function(e){this.renderableComponents.indexOf(e)===-1&&this.renderableComponents.push(e)},removeRenderableComponent:function(e){this.renderableComponents.indexOf(e)!==-1&&this.renderableComponents.splice(this.renderableComponents.indexOf(e),1)},prepareRenderableFrame:function(e){this.checkLayerLimits(e)},checkTransparency:function(){this.finalTransform.mProp.o.v<=0?!this.isTransparent&&this.globalData.renderConfig.hideOnTransparent&&(this.isTransparent=!0,this.hide()):this.isTransparent&&(this.isTransparent=!1,this.show())},checkLayerLimits:function(e){this.data.ip-this.data.st<=e&&this.data.op-this.data.st>e?this.isInRange!==!0&&(this.globalData._mdf=!0,this._mdf=!0,this.isInRange=!0,this.show()):this.isInRange!==!1&&(this.globalData._mdf=!0,this.isInRange=!1,this.hide())},renderRenderable:function(){var e,t=this.renderableComponents.length;for(e=0;e.1)&&this.audio.seek(this._currentTime/this.globalData.frameRate):(this.audio.play(),this.audio.seek(this._currentTime/this.globalData.frameRate),this._isPlaying=!0))},mn.prototype.show=function(){},mn.prototype.hide=function(){this.audio.pause(),this._isPlaying=!1},mn.prototype.pause=function(){this.audio.pause(),this._isPlaying=!1,this._canPlay=!1},mn.prototype.resume=function(){this._canPlay=!0},mn.prototype.setRate=function(e){this.audio.rate(e)},mn.prototype.volume=function(e){this._volumeMultiplier=e,this._previousVolume=e*this._volume,this.audio.volume(this._previousVolume)},mn.prototype.getBaseElement=function(){return null},mn.prototype.destroy=function(){},mn.prototype.sourceRectAtTime=function(){},mn.prototype.initExpressions=function(){};function hn(){}hn.prototype.checkLayers=function(e){var t,n=this.layers.length,r;for(this.completeLayers=!0,t=n-1;t>=0;--t)this.elements[t]||(r=this.layers[t],r.ip-r.st<=e-this.layers[t].st&&r.op-r.st>e-this.layers[t].st&&this.buildItem(t)),this.completeLayers=this.elements[t]?this.completeLayers:!1;this.checkPendingElements()},hn.prototype.createItem=function(e){switch(e.ty){case 2:return this.createImage(e);case 0:return this.createComp(e);case 1:return this.createSolid(e);case 3:return this.createNull(e);case 4:return this.createShape(e);case 5:return this.createText(e);case 6:return this.createAudio(e);case 13:return this.createCamera(e);case 15:return this.createFootage(e);default:return this.createNull(e)}},hn.prototype.createCamera=function(){throw Error(`You're using a 3d camera. Try the html renderer.`)},hn.prototype.createAudio=function(e){return new mn(e,this.globalData,this)},hn.prototype.createFootage=function(e){return new pn(e,this.globalData,this)},hn.prototype.buildAllItems=function(){var e,t=this.layers.length;for(e=0;e0&&(this.maskElement.setAttribute(`id`,p),this.element.maskedElement.setAttribute(b,`url(`+c()+`#`+p+`)`),r.appendChild(this.maskElement)),this.viewData.length&&this.element.addRenderableComponent(this)}vn.prototype.getMaskProperty=function(e){return this.viewData[e].prop},vn.prototype.renderFrame=function(e){var t=this.element.finalTransform.mat,n,r=this.masksProperties.length;for(n=0;n1&&(r+=` C`+t.o[i-1][0]+`,`+t.o[i-1][1]+` `+t.i[0][0]+`,`+t.i[0][1]+` `+t.v[0][0]+`,`+t.v[0][1]),n.lastPath!==r){var o=``;n.elem&&(t.c&&(o=e.inv?this.solidPath+r:r),n.elem.setAttribute(`d`,o)),n.lastPath=r}},vn.prototype.destroy=function(){this.element=null,this.globalData=null,this.maskElement=null,this.data=null,this.masksProperties=null};var yn=function(){var e={};e.createFilter=t,e.createAlphaToLuminanceFilter=n;function t(e,t){var n=L(`filter`);return n.setAttribute(`id`,e),t!==!0&&(n.setAttribute(`filterUnits`,`objectBoundingBox`),n.setAttribute(`x`,`0%`),n.setAttribute(`y`,`0%`),n.setAttribute(`width`,`100%`),n.setAttribute(`height`,`100%`)),n}function n(){var e=L(`feColorMatrix`);return e.setAttribute(`type`,`matrix`),e.setAttribute(`color-interpolation-filters`,`sRGB`),e.setAttribute(`values`,`0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1`),e}return e}(),bn=function(){var e={maskType:!0,svgLumaHidden:!0,offscreenCanvas:typeof OffscreenCanvas<`u`};return(/MSIE 10/i.test(navigator.userAgent)||/MSIE 9/i.test(navigator.userAgent)||/rv:11.0/i.test(navigator.userAgent)||/Edge\/\d./i.test(navigator.userAgent))&&(e.maskType=!1),/firefox/i.test(navigator.userAgent)&&(e.svgLumaHidden=!1),e}(),xn={},Z=`filter_result_`;function Sn(e){var t,n=`SourceGraphic`,r=e.data.ef?e.data.ef.length:0,i=te(),a=yn.createFilter(i,!0),o=0;this.filters=[];var s;for(t=0;t=0&&(n=this.shapeModifiers[e].processShapes(this._isFirstFrame),!n);--e);}},searchProcessedElement:function(e){for(var t=this.processedElements,n=0,r=t.length;n.01)return!1;n+=1}return!0},Ln.prototype.checkCollapsable=function(){if(this.o.length/2!=this.c.length/4)return!1;if(this.data.k.k[0].s)for(var e=0,t=this.data.k.k.length;e0;)c=r.transformers[g].mProps._mdf||c,--h,--g;if(c)for(h=f-r.styles[u].lvl,g=r.transformers.length-1;h>0;)m.multiply(r.transformers[g].mProps.v),--h,--g}else m=e;if(p=r.sh.paths,o=p._length,c){for(s=``,a=0;a=1?v=.99:v<=-1&&(v=-.99);var y=g*v,b=Math.cos(_+t.a.v)*y+a[0],x=Math.sin(_+t.a.v)*y+a[1];r.setAttribute(`fx`,b),r.setAttribute(`fy`,x),i&&!t.g._collapsable&&(t.of.setAttribute(`fx`,b),t.of.setAttribute(`fy`,x))}}}function u(e,t,n){var r=t.style,i=t.d;i&&(i._mdf||n)&&i.dashStr&&(r.pElem.setAttribute(`stroke-dasharray`,i.dashStr),r.pElem.setAttribute(`stroke-dashoffset`,i.dashoffset[0])),t.c&&(t.c._mdf||n)&&r.pElem.setAttribute(`stroke`,`rgb(`+C(t.c.v[0])+`,`+C(t.c.v[1])+`,`+C(t.c.v[2])+`)`),(t.o._mdf||n)&&r.pElem.setAttribute(`stroke-opacity`,t.o.v),(t.w._mdf||n)&&(r.pElem.setAttribute(`stroke-width`,t.w.v),r.msElem&&r.msElem.setAttribute(`stroke-width`,t.w.v))}return n}();function Wn(e,t,n){this.shapes=[],this.shapesData=e.shapes,this.stylesList=[],this.shapeModifiers=[],this.itemsData=[],this.processedElements=[],this.animatedContents=[],this.initElement(e,t,n),this.prevViewData=[]}u([dn,_n,Cn,On,wn,fn,Tn],Wn),Wn.prototype.initSecondaryElement=function(){},Wn.prototype.identityMatrix=new B,Wn.prototype.buildExpressionInterface=function(){},Wn.prototype.createContent=function(){this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,this.layerElement,0,[],!0),this.filterUniqueShapes()},Wn.prototype.filterUniqueShapes=function(){var e,t=this.shapes.length,n,r,i=this.stylesList.length,a,o=[],s=!1;for(r=0;r1&&s&&this.setShapesAsAnimated(o)}},Wn.prototype.setShapesAsAnimated=function(e){var t,n=e.length;for(t=0;t=0;--c){if(g=this.searchProcessedElement(e[c]),g?t[c]=n[g-1]:e[c]._render=o,e[c].ty===`fl`||e[c].ty===`st`||e[c].ty===`gf`||e[c].ty===`gs`||e[c].ty===`no`)g?t[c].style.closed=e[c].hd:t[c]=this.createStyleElement(e[c],i),e[c]._render&&t[c].style.pElem.parentNode!==r&&r.appendChild(t[c].style.pElem),f.push(t[c].style);else if(e[c].ty===`gr`){if(!g)t[c]=this.createGroupElement(e[c]);else for(d=t[c].it.length,u=0;u1,this.kf&&this.addEffect(this.getKeyframeValue.bind(this)),this.kf},Kn.prototype.addEffect=function(e){this.effectsSequence.push(e),this.elem.addDynamicProperty(this)},Kn.prototype.getValue=function(e){if(!((this.elem.globalData.frameId===this.frameId||!this.effectsSequence.length)&&!e)){this.currentData.t=this.data.d.k[this.keysIndex].s.t;var t=this.currentData,n=this.keysIndex;if(this.lock){this.setCurrentData(this.currentData);return}this.lock=!0,this._mdf=!1;var r,i=this.effectsSequence.length,a=e||this.data.d.k[this.keysIndex].s;for(r=0;rt);)n+=1;return this.keysIndex!==n&&(this.keysIndex=n),this.data.d.k[this.keysIndex].s},Kn.prototype.buildFinalText=function(e){for(var t=[],n=0,r=e.length,i,a,o=!1,s=!1,c=``;n=55296&&i<=56319?Yt.isRegionalFlag(e,n)?c=e.substr(n,14):(a=e.charCodeAt(n+1),a>=56320&&a<=57343&&(Yt.isModifier(i,a)?(c=e.substr(n,2),o=!0):c=Yt.isFlagEmoji(e.substr(n,4))?e.substr(n,4):e.substr(n,2))):i>56319?(a=e.charCodeAt(n+1),Yt.isVariationSelector(i)&&(o=!0)):Yt.isZeroWidthJoiner(i)&&(o=!0,s=!0),o?(t[t.length-1]+=c,o=!1):t.push(c),n+=c.length;return t},Kn.prototype.completeTextData=function(e){e.__complete=!0;var t=this.elem.globalData.fontManager,n=this.data,r=[],i,a,o,s=0,c,l=n.m.g,u=0,d=0,f=0,p=[],m=0,h=0,g,_,v=t.getFontByName(e.f),y,b=0,x=Jt(v);e.fWeight=x.weight,e.fStyle=x.style,e.finalSize=e.s,e.finalText=this.buildFinalText(e.t),a=e.finalText.length,e.finalLineHeight=e.lh;var S=e.tr/1e3*e.finalSize,C;if(e.sz)for(var w=!0,T=e.sz[0],E=e.sz[1],D,O;w;){O=this.buildFinalText(e.t),D=0,m=0,a=O.length,S=e.tr/1e3*e.finalSize;var k=-1;for(i=0;iT&&O[i]!==` `?(k===-1?a+=1:i=k,D+=e.finalLineHeight||e.finalSize*1.2,O.splice(i,+(k===i),`\r`),k=-1,m=0):(m+=b,m+=S);D+=v.ascent*e.finalSize/100,this.canResize&&e.finalSize>this.minimumFontSize&&Eh?m:h,m=-2*S,c=``,o=!0,f+=1):c=j,t.chars?(y=t.getCharData(j,v.fStyle,t.getFontByName(e.f).fFamily),b=o?0:y.w*e.finalSize/100):b=t.measureText(c,e.f,e.finalSize),j===` `?A+=b+S:(m+=b+S+A,A=0),r.push({l:b,an:b,add:u,n:o,anIndexes:[],val:c,line:f,animatorJustifyOffset:0}),l==2){if(u+=b,c===``||c===` `||i===a-1){for((c===``||c===` `)&&(u-=b);d<=i;)r[d].an=u,r[d].ind=s,r[d].extra=b,d+=1;s+=1,u=0}}else if(l==3){if(u+=b,c===``||i===a-1){for(c===``&&(u-=b);d<=i;)r[d].an=u,r[d].ind=s,r[d].extra=b,d+=1;u=0,s+=1}}else r[s].ind=s,r[s].extra=0,s+=1;if(e.l=r,h=m>h?m:h,p.push(m),e.sz)e.boxWidth=e.sz[0],e.justifyOffset=0;else switch(e.boxWidth=h,e.j){case 1:e.justifyOffset=-e.boxWidth;break;case 2:e.justifyOffset=-e.boxWidth/2;break;default:e.justifyOffset=0}e.lineWidths=p;var M=n.a,N,P;_=M.length;var ee,te,F=[];for(g=0;g<_;g+=1){for(N=M[g],N.a.sc&&(e.strokeColorAnim=!0),N.a.sw&&(e.strokeWidthAnim=!0),(N.a.fc||N.a.fh||N.a.fs||N.a.fb)&&(e.fillColorAnim=!0),te=0,ee=N.s.b,i=0;i0?i=this.ne.v/100:a=-this.ne.v/100,this.xe.v>0?o=1-this.xe.v/100:s=1+this.xe.v/100;var c=Te.getBezierEasing(i,a,o,s).get,l=0,u=this.finalS,d=this.finalE,f=this.data.sh;if(f===2)l=d===u?+(r>=d):e(0,t(.5/(d-u)+(r-u)/(d-u),1)),l=c(l);else if(f===3)l=d===u?r>=d?0:1:1-e(0,t(.5/(d-u)+(r-u)/(d-u),1)),l=c(l);else if(f===4)d===u?l=0:(l=e(0,t(.5/(d-u)+(r-u)/(d-u),1)),l<.5?l*=2:l=1-2*(l-.5)),l=c(l);else if(f===5){if(d===u)l=0;else{var p=d-u;r=t(e(0,r+.5-u),d-u);var m=-p/2+r,h=p/2;l=Math.sqrt(1-m*m/(h*h))}l=c(l)}else f===6?(d===u?l=0:(r=t(e(0,r+.5-u),d-u),l=(1+Math.cos(Math.PI+Math.PI*2*r/(d-u)))/2),l=c(l)):(r>=n(u)&&(l=r-u<0?e(0,t(t(d,1)-(u-r),1)):e(0,t(d-r,1))),l=c(l));if(this.sm.v!==100){var g=this.sm.v*.01;g===0&&(g=1e-8);var _=.5-g*.5;l<_?l=0:(l=(l-_)/g,l>1&&(l=1))}return l*this.a.v},getValue:function(e){this.iterateDynamicProperties(),this._mdf=e||this._mdf,this._currentTextLength=this.elem.textProperty.currentData.l.length||0,e&&this.data.r===2&&(this.e.v=this._currentTextLength);var t=this.data.r===2?1:100/this.data.totalChars,n=this.o.v/t,r=this.s.v/t+n,i=this.e.v/t+n;if(r>i){var a=r;r=i,i=a}this.finalS=r,this.finalE=i}},u([Ke],r);function i(e,t,n){return new r(e,t,n)}return{getTextSelectorProp:i}}();function Jn(e,t,n){var r={propType:!1},i=z.getProp,a=t.a;this.a={r:a.r?i(e,a.r,0,D,n):r,rx:a.rx?i(e,a.rx,0,D,n):r,ry:a.ry?i(e,a.ry,0,D,n):r,sk:a.sk?i(e,a.sk,0,D,n):r,sa:a.sa?i(e,a.sa,0,D,n):r,s:a.s?i(e,a.s,1,.01,n):r,a:a.a?i(e,a.a,1,0,n):r,o:a.o?i(e,a.o,0,.01,n):r,p:a.p?i(e,a.p,1,0,n):r,sw:a.sw?i(e,a.sw,0,0,n):r,sc:a.sc?i(e,a.sc,1,0,n):r,fc:a.fc?i(e,a.fc,1,0,n):r,fh:a.fh?i(e,a.fh,0,0,n):r,fs:a.fs?i(e,a.fs,0,.01,n):r,fb:a.fb?i(e,a.fb,0,.01,n):r,t:a.t?i(e,a.t,0,0,n):r},this.s=qn.getTextSelectorProp(e,t.s,n),this.s.t=t.s.t}function Yn(e,t,n){this._isFirstFrame=!0,this._hasMaskedPath=!1,this._frameId=-1,this._textData=e,this._renderType=t,this._elem=n,this._animatorsData=m(this._textData.a.length),this._pathData={},this._moreOptions={alignment:{}},this.renderedLetters=[],this.lettersChangedFlag=!1,this.initDynamicPropertyContainer(n)}Yn.prototype.searchProperties=function(){var e,t=this._textData.a.length,n,r=z.getProp;for(e=0;e=m+Ee||!x?(T=(m+Ee-g)/h.partialLength,oe=b.point[0]+(h.point[0]-b.point[0])*T,se=b.point[1]+(h.point[1]-b.point[1])*T,a.translate(-n[0]*f[u].an*.005,-(n[1]*A)*.01),_=!1):x&&(g+=h.partialLength,v+=1,v>=x.length&&(v=0,y+=1,S[y]?x=S[y].points:D.v.c?(v=0,y=0,x=S[y].points):(g-=h.partialLength,x=null)),x&&(b=h,h=x[v],C=h.partialLength));ae=f[u].an/2-f[u].add,a.translate(-ae,0,0)}else ae=f[u].an/2-f[u].add,a.translate(-ae,0,0),a.translate(-n[0]*f[u].an*.005,-n[1]*A*.01,0);for(P=0;Pe?this.textSpans[e].span:L(s?`g`:`text`),b<=e){if(c.setAttribute(`stroke-linecap`,`butt`),c.setAttribute(`stroke-linejoin`,`round`),c.setAttribute(`stroke-miterlimit`,`4`),this.textSpans[e].span=c,s){var S=L(`g`);c.appendChild(S),this.textSpans[e].childSpan=S}this.textSpans[e].span=c,this.layerElement.appendChild(c)}c.style.display=`inherit`}if(l.reset(),d&&(o[e].n&&(f=-g,p+=n.yOffset,p+=+!!h,h=!1),this.applyTextPropertiesToMatrix(n,l,o[e].line,f,p),f+=o[e].l||0,f+=g),s){x=this.globalData.fontManager.getCharData(n.finalText[e],r.fStyle,this.globalData.fontManager.getFontByName(n.f).fFamily);var C;if(x.t===1)C=new rr(x.data,this.globalData,this);else{var w=Zn;x.data&&x.data.shapes&&(w=this.buildShapeData(x.data,n.finalSize)),C=new Wn(w,this.globalData,this)}if(this.textSpans[e].glyph){var T=this.textSpans[e].glyph;this.textSpans[e].childSpan.removeChild(T.layerElement),T.destroy()}this.textSpans[e].glyph=C,C._debug=!0,C.prepareFrame(0),C.renderFrame(),this.textSpans[e].childSpan.appendChild(C.layerElement),x.t===1&&this.textSpans[e].childSpan.setAttribute(`transform`,`scale(`+n.finalSize/100+`,`+n.finalSize/100+`)`)}else d&&c.setAttribute(`transform`,`translate(`+l.props[12]+`,`+l.props[13]+`)`),c.textContent=o[e].val,c.setAttributeNS(`http://www.w3.org/XML/1998/namespace`,`xml:space`,`preserve`)}d&&c&&c.setAttribute(`d`,u)}for(;e=0;--t)(this.completeLayers||this.elements[t])&&this.elements[t].prepareFrame(e-this.layers[t].st);if(this.globalData._mdf)for(t=0;t=0;--n)(this.completeLayers||this.elements[n])&&(this.elements[n].prepareFrame(this.renderedFrame-this.layers[n].st),this.elements[n]._mdf&&(this._mdf=!0))}},nr.prototype.renderInnerContent=function(){var e,t=this.layers.length;for(e=0;e=0;--n)e.finalTransform.multiply(e.transforms[n].transform.mProps.v);e._mdf=i},processSequences:function(e){var t,n=this.sequenceList.length;for(t=0;t=1){this.buffers=[];var e=this.globalData.canvasContext,t=cr.createCanvas(e.canvas.width,e.canvas.height);this.buffers.push(t);var n=cr.createCanvas(e.canvas.width,e.canvas.height);this.buffers.push(n),this.data.tt>=3&&!document._isProxy&&cr.loadLumaCanvas()}this.canvasContext=this.globalData.canvasContext,this.transformCanvas=this.globalData.transformCanvas,this.renderableEffectsManager=new ur(this),this.searchEffectTransforms()},createContent:function(){},setBlendMode:function(){var e=this.globalData;if(e.blendMode!==this.data.bm){e.blendMode=this.data.bm;var t=$t(this.data.bm);e.canvasContext.globalCompositeOperation=t}},createRenderableComponents:function(){this.maskManager=new dr(this.data,this),this.transformEffects=this.renderableEffectsManager.getEffects(gn.TRANSFORM_EFFECT)},hideElement:function(){!this.hidden&&(!this.isInRange||this.isTransparent)&&(this.hidden=!0)},showElement:function(){this.isInRange&&!this.isTransparent&&(this.hidden=!1,this._isFirstFrame=!0,this.maskManager._isFirstFrame=!0)},clearCanvas:function(e){e.clearRect(this.transformCanvas.tx,this.transformCanvas.ty,this.transformCanvas.w*this.transformCanvas.sx,this.transformCanvas.h*this.transformCanvas.sy)},prepareLayer:function(){if(this.data.tt>=1){var e=this.buffers[0].getContext(`2d`);this.clearCanvas(e),e.drawImage(this.canvasContext.canvas,0,0),this.currentTransform=this.canvasContext.getTransform(),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform)}},exitLayer:function(){if(this.data.tt>=1){var e=this.buffers[1],t=e.getContext(`2d`);if(this.clearCanvas(t),t.drawImage(this.canvasContext.canvas,0,0),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform),this.comp.getElementById(`tp`in this.data?this.data.tp:this.data.ind-1).renderFrame(!0),this.canvasContext.setTransform(1,0,0,1,0,0),this.data.tt>=3&&!document._isProxy){var n=cr.getLumaCanvas(this.canvasContext.canvas);n.getContext(`2d`).drawImage(this.canvasContext.canvas,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.drawImage(n,0,0)}this.canvasContext.globalCompositeOperation=pr[this.data.tt],this.canvasContext.drawImage(e,0,0),this.canvasContext.globalCompositeOperation=`destination-over`,this.canvasContext.drawImage(this.buffers[0],0,0),this.canvasContext.setTransform(this.currentTransform),this.canvasContext.globalCompositeOperation=`source-over`}},renderFrame:function(e){if(!(this.hidden||this.data.hd)&&!(this.data.td===1&&!e)){this.renderTransform(),this.renderRenderable(),this.renderLocalTransform(),this.setBlendMode();var t=this.data.ty===0;this.prepareLayer(),this.globalData.renderer.save(t),this.globalData.renderer.ctxTransform(this.finalTransform.localMat.props),this.globalData.renderer.ctxOpacity(this.finalTransform.localOpacity),this.renderInnerContent(),this.globalData.renderer.restore(t),this.exitLayer(),this.maskManager.hasMasks&&this.globalData.renderer.restore(!0),this._isFirstFrame&&=!1}},destroy:function(){this.canvasContext=null,this.data=null,this.globalData=null,this.maskManager.destroy()},mHelper:new B},fr.prototype.hide=fr.prototype.hideElement,fr.prototype.show=fr.prototype.showElement;function mr(e,t,n,r){this.styledShapes=[],this.tr=[0,0,0,0,0,0];var i=4;t.ty===`rc`?i=5:t.ty===`el`?i=6:t.ty===`sr`&&(i=7),this.sh=Qe.getShapeProp(e,t,i,e);var a,o=n.length,s;for(a=0;a=0;--a){if(d=this.searchProcessedElement(e[a]),d?t[a]=n[d-1]:e[a]._shouldRender=r,e[a].ty===`fl`||e[a].ty===`st`||e[a].ty===`gf`||e[a].ty===`gs`)d?t[a].style.closed=!1:t[a]=this.createStyleElement(e[a],m),l.push(t[a].style);else if(e[a].ty===`gr`){if(!d)t[a]=this.createGroupElement(e[a]);else for(c=t[a].it.length,s=0;s=0;--i)t[i].ty===`tr`?(o=n[i].transform,this.renderShapeTransform(e,o)):t[i].ty===`sh`||t[i].ty===`el`||t[i].ty===`rc`||t[i].ty===`sr`?this.renderPath(t[i],n[i]):t[i].ty===`fl`?this.renderFill(t[i],n[i],o):t[i].ty===`st`?this.renderStroke(t[i],n[i],o):t[i].ty===`gf`||t[i].ty===`gs`?this.renderGradientFill(t[i],n[i],o):t[i].ty===`gr`?this.renderShape(o,t[i].it,n[i].it):t[i].ty;r&&this.drawLayer()},hr.prototype.renderStyledShape=function(e,t){if(this._isFirstFrame||t._mdf||e.transforms._mdf){var n=e.trNodes,r=t.paths,i,a,o,s=r._length;n.length=0;var c=e.transforms.finalTransform;for(o=0;o=1?u=.99:u<=-1&&(u=-.99);var d=c*u,f=Math.cos(l+t.a.v)*d+o[0],p=Math.sin(l+t.a.v)*d+o[1];i=a.createRadialGradient(f,p,0,o[0],o[1],c)}var m,h=e.g.p,g=t.g.c,_=1;for(m=0;ma&&c===`xMidYMid slice`||ii&&s===`meet`||ai&&s===`slice`)?this.transformCanvas.tx=(n-this.transformCanvas.w*(r/this.transformCanvas.h))/2*this.renderConfig.dpr:l===`xMax`&&(ai&&s===`slice`)?this.transformCanvas.tx=(n-this.transformCanvas.w*(r/this.transformCanvas.h))*this.renderConfig.dpr:this.transformCanvas.tx=0,u===`YMid`&&(a>i&&s===`meet`||ai&&s===`meet`||a=0;--e)this.elements[e]&&this.elements[e].destroy&&this.elements[e].destroy();this.elements.length=0,this.globalData.canvasContext=null,this.animationItem.container=null,this.destroyed=!0},Q.prototype.renderFrame=function(e,t){if(!(this.renderedFrame===e&&this.renderConfig.clearCanvas===!0&&!t||this.destroyed||e===-1)){this.renderedFrame=e,this.globalData.frameNum=e-this.animationItem._isFirstFrame,this.globalData.frameId+=1,this.globalData._mdf=!this.renderConfig.clearCanvas||t,this.globalData.projectInterface.currentFrame=e;var n,r=this.layers.length;for(this.completeLayers||this.checkLayers(e),n=r-1;n>=0;--n)(this.completeLayers||this.elements[n])&&this.elements[n].prepareFrame(e-this.layers[n].st);if(this.globalData._mdf){for(this.renderConfig.clearCanvas===!0?this.canvasContext.clearRect(0,0,this.transformCanvas.w,this.transformCanvas.h):this.save(),n=r-1;n>=0;--n)(this.completeLayers||this.elements[n])&&this.elements[n].renderFrame();this.renderConfig.clearCanvas!==!0&&this.restore()}}},Q.prototype.buildItem=function(e){var t=this.elements;if(!(t[e]||this.layers[e].ty===99)){var n=this.createItem(this.layers[e],this,this.globalData);t[e]=n,n.initExpressions()}},Q.prototype.checkPendingElements=function(){for(;this.pendingElements.length;)this.pendingElements.pop().checkParenting()},Q.prototype.hide=function(){this.animationItem.container.style.display=`none`},Q.prototype.show=function(){this.animationItem.container.style.display=`block`};function yr(){this.opacity=-1,this.transform=p(`float32`,16),this.fillStyle=``,this.strokeStyle=``,this.lineWidth=``,this.lineCap=``,this.lineJoin=``,this.miterLimit=``,this.id=Math.random()}function br(){this.stack=[],this.cArrPos=0,this.cTr=new B;var e,t=15;for(e=0;e=0;--t)(this.completeLayers||this.elements[t])&&this.elements[t].renderFrame()},xr.prototype.destroy=function(){var e;for(e=this.layers.length-1;e>=0;--e)this.elements[e]&&this.elements[e].destroy();this.layers=null,this.elements=null},xr.prototype.createComp=function(e){return new xr(e,this.globalData,this)};function Sr(e,t){this.animationItem=e,this.renderConfig={clearCanvas:t&&t.clearCanvas!==void 0?t.clearCanvas:!0,context:t&&t.context||null,progressiveLoad:t&&t.progressiveLoad||!1,preserveAspectRatio:t&&t.preserveAspectRatio||`xMidYMid meet`,imagePreserveAspectRatio:t&&t.imagePreserveAspectRatio||`xMidYMid slice`,contentVisibility:t&&t.contentVisibility||`visible`,className:t&&t.className||``,id:t&&t.id||``,runExpressions:!t||t.runExpressions===void 0||t.runExpressions},this.renderConfig.dpr=t&&t.dpr||1,this.animationItem.wrapper&&(this.renderConfig.dpr=t&&t.dpr||window.devicePixelRatio||1),this.renderedFrame=-1,this.globalData={frameNum:-1,_mdf:!1,renderConfig:this.renderConfig,currentGlobalAlpha:-1},this.contextData=new br,this.elements=[],this.pendingElements=[],this.transformMat=new B,this.completeLayers=!1,this.rendererType=`canvas`,this.renderConfig.clearCanvas&&(this.ctxTransform=this.contextData.transform.bind(this.contextData),this.ctxOpacity=this.contextData.opacity.bind(this.contextData),this.ctxFillStyle=this.contextData.fillStyle.bind(this.contextData),this.ctxStrokeStyle=this.contextData.strokeStyle.bind(this.contextData),this.ctxLineWidth=this.contextData.lineWidth.bind(this.contextData),this.ctxLineCap=this.contextData.lineCap.bind(this.contextData),this.ctxLineJoin=this.contextData.lineJoin.bind(this.contextData),this.ctxMiterLimit=this.contextData.miterLimit.bind(this.contextData),this.ctxFill=this.contextData.fill.bind(this.contextData),this.ctxFillRect=this.contextData.fillRect.bind(this.contextData),this.ctxStroke=this.contextData.stroke.bind(this.contextData),this.save=this.contextData.save.bind(this.contextData))}return u([Q],Sr),Sr.prototype.createComp=function(e){return new xr(e,this.globalData,this)},be(`canvas`,Sr),vt.registerModifier(`tm`,bt),vt.registerModifier(`pb`,xt),vt.registerModifier(`rp`,St),vt.registerModifier(`rd`,Ct),vt.registerModifier(`zz`,K),vt.registerModifier(`op`,qt),V}))}))(),1);function Cr({documentID:e,className:t=``,showError:n=!0}){let r=(0,g.useRef)(null),i=(0,g.useRef)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(null);return(0,g.useEffect)(()=>{let t=!1,n=null;return o(``),c(null),fetch(k.stickerDocumentAnimationURL(e),{credentials:`same-origin`}).then(async e=>{if(!e.ok){let t=await e.json().catch(()=>null);throw Error(t?.error||e.statusText)}if((e.headers.get(`content-type`)??``).includes(`json`)){let n=await e.json();if(t||!r.current)return;i.current?.destroy(),i.current=Sr.default.loadAnimation({container:r.current,renderer:`canvas`,loop:!0,autoplay:!0,animationData:n});return}let a=await e.blob();t||(n=URL.createObjectURL(a),c(n))}).catch(e=>{t||o(O(e))}),()=>{t=!0,i.current?.destroy(),i.current=null,n&&URL.revokeObjectURL(n)}},[e]),(0,W.jsxs)(`div`,{className:`sticker-doc-cell ${t}`.trim(),children:[s?(0,W.jsx)(`img`,{className:`sticker-doc-image`,src:s,alt:``}):(0,W.jsx)(`div`,{className:`sticker-doc-canvas`,ref:r}),a&&n&&(0,W.jsx)(`span`,{className:`sticker-doc-error`,children:a})]})}function wr({kind:e,onClose:t,onCreated:n}){let r=e===`emoji`?`emoji`:`sticker`,[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(``),[u,d]=(0,g.useState)(null),[f,p]=(0,g.useState)(``),[m,h]=(0,g.useState)(!1),[_,v]=(0,g.useState)(``);async function y(){if(!i.trim()||!o.trim()||!c.trim()||!u){v(`Title, short name, emoji and a first ${r} file are required.`);return}if(!f.trim()){v(`Please enter an operation reason`);return}h(!0),v(``);try{let r=new FormData;r.set(`metadata`,JSON.stringify({command_id:``,reason:f.trim(),confirm:!0,title:i.trim(),short_name:o.trim().toLowerCase(),kind:e,emoji:c.trim()})),r.set(`file`,u,u.name),await k.createStickerSet(r),n(),t()}catch(e){v(O(e))}finally{h(!1)}}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Create a new ${r} pack`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`New set`}),(0,W.jsx)(`h2`,{children:`Create a new ${r} pack`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:t,disabled:m,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsxs)(`div`,{className:`gift-fields-grid`,children:[(0,W.jsxs)(`label`,{children:[(0,W.jsx)(`span`,{children:`Title`}),(0,W.jsx)(`input`,{value:i,maxLength:64,onChange:e=>a(e.target.value)})]}),(0,W.jsxs)(`label`,{children:[(0,W.jsx)(`span`,{children:`Short name`}),(0,W.jsx)(`input`,{value:o,maxLength:32,onChange:e=>s(e.target.value),placeholder:`lowercase_short_name`})]}),(0,W.jsxs)(`label`,{children:[(0,W.jsx)(`span`,{children:`Emoji`}),(0,W.jsx)(`input`,{value:c,onChange:e=>l(e.target.value),placeholder:`e.g. 😀`})]})]}),(0,W.jsxs)(`label`,{className:`gift-file-picker ${u?`has-file`:``}`,children:[(0,W.jsx)(`input`,{type:`file`,accept:`.tgs,.json,.webp,application/json,application/x-tgsticker,image/webp`,onChange:e=>d(e.target.files?.[0]??null)}),(0,W.jsxs)(`span`,{className:`gift-file-copy`,children:[(0,W.jsx)(`span`,{className:`gift-field-label`,children:`First ${r}`}),(0,W.jsx)(`strong`,{children:u?u.name:`Choose a TGS, Lottie JSON, or WebP file`})]}),(0,W.jsx)(`span`,{className:`gift-file-action`,children:u?`Change file`:`Choose file`})]}),(0,W.jsxs)(`label`,{className:`gift-reason-field`,children:[(0,W.jsx)(`span`,{children:`Audit reason`}),(0,W.jsx)(`input`,{value:f,placeholder:`Briefly describe why this gift is being imported`,onChange:e=>p(e.target.value)})]}),_&&(0,W.jsx)(q,{children:_})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:t,disabled:m,children:`Close`}),(0,W.jsxs)(`button`,{className:`btn primary`,type:`button`,onClick:y,disabled:m,children:[m?(0,W.jsx)(I,{className:`spin`,size:15}):(0,W.jsx)(ht,{size:15}),`Create ${r} pack`]})]})]})}),document.body)}var Tr=24;function Er({set:e,onClose:t}){let n=e.Kind===`emoji`?`emoji`:`sticker`,[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(1),l=(0,g.useCallback)(()=>{let t=!1;return o(``),k.stickerSetDocuments(e.ID).then(e=>{t||i(e.document_ids??[])}).catch(e=>{t||o(O(e))}),()=>{t=!0}},[e.ID]);(0,g.useEffect)(()=>(i(null),c(1),l()),[l]);let u=r?.length??0,d=Math.max(1,Math.ceil(u/Tr)),f=Math.min(s,d),p=(f-1)*Tr,m=r?.slice(p,p+Tr)??[],h=m.length===0?0:p+1,_=h===0?0:h+m.length-1;return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal sticker-preview-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":e.Title||`#${e.ID}`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Set contents`}),(0,W.jsx)(`h2`,{children:e.Title||`#${e.ID}`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:t,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsx)(Dr,{setID:e.ID,noun:n,onAdded:l}),a&&(0,W.jsx)(q,{children:a}),!a&&r===null&&(0,W.jsxs)(`div`,{className:`loading-line`,children:[(0,W.jsx)(I,{className:`spin`,size:18}),` `,`Loading`]}),r!==null&&u===0&&!a&&(0,W.jsx)(`div`,{className:`empty-panel`,children:`This set has no documents.`}),m.length>0&&(0,W.jsx)(`div`,{className:`sticker-doc-grid`,children:m.map(t=>(0,W.jsxs)(`div`,{className:`sticker-doc-grid-cell`,children:[(0,W.jsx)(Cr,{documentID:t}),(0,W.jsx)(Z,{compact:!0,tone:`danger`,label:`Remove`,icon:(0,W.jsx)(pt,{size:12}),path:`/api/actions/remove-sticker-from-set`,payload:()=>({set_id:e.ID,document_id:t}),onDone:l})]},t))},f),u>Tr&&(0,W.jsxs)(`div`,{className:`gift-pager`,children:[(0,W.jsx)(`span`,{className:`gift-pager-range`,children:`Showing ${h}-${_} of ${u}`}),(0,W.jsxs)(`div`,{className:`gift-pager-controls`,children:[(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>c(e=>Math.max(1,e-1)),disabled:f<=1,children:[(0,W.jsx)(xe,{size:14}),` `,`Previous`]}),(0,W.jsx)(`span`,{className:`gift-pager-page`,children:`Page ${f} of ${d}`}),(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>c(e=>Math.min(d,e+1)),disabled:f>=d,children:[`Next`,` `,(0,W.jsx)(Se,{size:14})]})]})]})]})]})}),document.body)}function Dr({setID:e,noun:t,onAdded:n}){let[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(!1),[d,f]=(0,g.useState)(``);async function p(){if(!r){f(`Choose a ${t} file first`);return}if(!a.trim()){f(`An emoji is required.`);return}if(!s.trim()){f(`Please enter an operation reason`);return}u(!0),f(``);try{let t=new FormData;t.set(`metadata`,JSON.stringify({command_id:``,reason:s.trim(),confirm:!0,set_id:e,emoji:a.trim()})),t.set(`file`,r,r.name),await k.addStickerToSet(t),i(null),o(``),c(``),n()}catch(e){f(O(e))}finally{u(!1)}}return(0,W.jsxs)(`div`,{className:`sticker-add-form`,children:[(0,W.jsxs)(`label`,{className:`gift-file-picker compact ${r?`has-file`:``}`,children:[(0,W.jsx)(`input`,{type:`file`,accept:`.tgs,.json,.webp,application/json,application/x-tgsticker,image/webp`,onChange:e=>i(e.target.files?.[0]??null)}),(0,W.jsx)(`span`,{className:`gift-file-copy`,children:(0,W.jsx)(`strong`,{children:r?r.name:`Choose a TGS, Lottie JSON, or WebP file`})})]}),(0,W.jsx)(`input`,{className:`small-input`,value:a,onChange:e=>o(e.target.value),placeholder:`e.g. 😀`}),(0,W.jsx)(`input`,{className:`small-input`,value:s,onChange:e=>c(e.target.value),placeholder:`Describe why this operation is being performed`}),(0,W.jsxs)(`button`,{className:`btn primary compact-btn`,type:`button`,onClick:p,disabled:l,children:[l?(0,W.jsx)(I,{className:`spin`,size:14}):(0,W.jsx)(Ye,{size:14}),` `,`Add ${t}`]}),d&&(0,W.jsx)(`span`,{className:`sticker-add-form-error`,children:d})]})}function Or({kind:e}){let[t,n]=(0,g.useState)([]),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(!1),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(10),[d,f]=(0,g.useState)(1),[p,m]=(0,g.useState)({}),[h,_]=(0,g.useState)({}),[v,y]=(0,g.useState)(null),[b,x]=(0,g.useState)(!1),S=e===`emoji`?`Emoji`:`Stickers`,C=e===`emoji`?`Custom-emoji packs — system packs aren't shown here, they're not hand-edited`:`Sticker packs — system packs (dice, animated emoji, gifts) aren't shown here, they're not hand-edited`,w=e===`emoji`?`emoji`:`sticker`;async function T(){o(!0),c(``);try{n((await k.stickerSets(e)).rows??[])}catch(e){c(O(e))}finally{o(!1)}}(0,g.useEffect)(()=>{T()},[e]);let E=(0,g.useMemo)(()=>{let e=r.trim().toLowerCase();return e?t.filter(t=>String(t.ID).includes(e)||t.ShortName.toLowerCase().includes(e)||t.Title.toLowerCase().includes(e)):t},[t,r]);(0,g.useEffect)(()=>{f(1)},[r,l,e]);let D=l===`all`?1:Math.max(1,Math.ceil(E.length/l)),A=Math.min(d,D),j=(0,g.useMemo)(()=>{if(l===`all`)return E;let e=(A-1)*l;return E.slice(e,e+l)},[E,A,l]),M=j.length===0?0:l===`all`?1:(A-1)*l+1,N=M===0?0:M+j.length-1,P=(0,g.useMemo)(()=>({total:t.length,official:t.filter(e=>e.Official).length,archived:t.filter(e=>e.Archived).length}),[t]);return(0,W.jsxs)(G,{title:S,eyebrow:C,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn`,type:`button`,onClick:()=>T(),disabled:a,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]}),(0,W.jsxs)(`button`,{className:`btn primary`,type:`button`,onClick:()=>x(!0),children:[(0,W.jsx)(Ye,{size:15}),` `,`Create ${w} pack`]})]}),children:[s&&(0,W.jsx)(q,{children:s}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Total sets`,value:String(P.total)}),(0,W.jsx)(Y,{label:`Official`,value:String(P.official),tone:`good`}),(0,W.jsx)(Y,{label:`Archived`,value:String(P.archived),tone:P.archived>0?`warn`:`neutral`})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`div`,{className:`toolbar`,children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`Search set ID, short name or title`})]}),(0,W.jsxs)(`label`,{className:`gift-page-size`,children:[(0,W.jsx)(`span`,{children:`Per page`}),(0,W.jsxs)(`select`,{value:String(l),onChange:e=>u(e.target.value===`all`?`all`:Number(e.target.value)),children:[(0,W.jsx)(`option`,{value:`10`,children:`10`}),(0,W.jsx)(`option`,{value:`20`,children:`20`}),(0,W.jsx)(`option`,{value:`50`,children:`50`}),(0,W.jsx)(`option`,{value:`100`,children:`100`}),(0,W.jsx)(`option`,{value:`all`,children:`All`})]})]}),(0,W.jsx)(`span`,{className:`gift-list-summary`,children:`Showing ${E.length} of ${t.length}`})]})}),(0,W.jsx)(`div`,{className:`table-wrap gift-table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Logo`}),(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Short name`}),(0,W.jsx)(`th`,{children:`Title`}),(0,W.jsx)(`th`,{children:`Documents`}),(0,W.jsx)(`th`,{children:`Official`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Sort order`}),(0,W.jsx)(`th`,{children:`Actions`})]})}),(0,W.jsxs)(`tbody`,{children:[j.map(e=>(0,W.jsxs)(`tr`,{className:e.Archived?`gift-row-disabled`:``,children:[(0,W.jsx)(`td`,{children:e.CoverDocumentID?(0,W.jsx)(Cr,{documentID:e.CoverDocumentID,className:`list-thumb`,showError:!1}):(0,W.jsx)(`div`,{className:`sticker-list-thumb-empty`,children:(0,W.jsx)(Ie,{size:14})})}),(0,W.jsx)(`td`,{className:`mono`,children:e.ID}),(0,W.jsx)(`td`,{className:`mono`,children:e.ShortName||(0,W.jsx)(`span`,{className:`muted-cell`,children:`None`})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`sort-order-editor`,children:[(0,W.jsx)(`input`,{className:`small-input title-input`,value:h[e.ID]??e.Title,onChange:t=>_(n=>({...n,[e.ID]:t.target.value}))}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:`Save`,path:`/api/actions/rename-sticker-set`,payload:()=>({set_id:e.ID,title:(h[e.ID]??e.Title).trim()}),onDone:()=>void T()})]})}),(0,W.jsx)(`td`,{children:e.Count}),(0,W.jsx)(`td`,{children:e.Official?(0,W.jsx)(J,{tone:`good`,children:`Yes`}):(0,W.jsx)(J,{children:`No`})}),(0,W.jsx)(`td`,{children:e.Archived?(0,W.jsx)(J,{tone:`danger`,children:`Archived`}):(0,W.jsx)(J,{tone:`good`,children:`Enabled`})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`sort-order-editor`,children:[(0,W.jsx)(`input`,{type:`number`,className:`small-input`,value:p[e.ID]??String(e.SortOrder),onChange:t=>m(n=>({...n,[e.ID]:t.target.value}))}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:`Save`,path:`/api/actions/set-sticker-set-sort-order`,payload:()=>({set_id:e.ID,sort_order:Number(p[e.ID]??e.SortOrder)}),onDone:()=>void T()})]})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`gift-table-actions`,children:[(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>y(e),children:[(0,W.jsx)(Oe,{size:13}),` `,`View`]}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:e.Archived?`Unarchive`:`Archive`,path:`/api/actions/set-sticker-set-archived`,payload:()=>({set_id:e.ID,archived:!e.Archived}),onDone:()=>void T()}),(0,W.jsx)(Z,{compact:!0,tone:`danger`,label:`Delete`,path:`/api/actions/delete-sticker-set`,payload:()=>({set_id:e.ID}),onDone:()=>void T()})]})})]},e.ID)),j.length===0&&(0,W.jsx)(Bt,{colSpan:9})]})]})}),l!==`all`&&E.length>0&&(0,W.jsxs)(`div`,{className:`gift-pager`,children:[(0,W.jsx)(`span`,{className:`gift-pager-range`,children:`Showing ${M}-${N} of ${E.length}`}),(0,W.jsxs)(`div`,{className:`gift-pager-controls`,children:[(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>f(e=>Math.max(1,e-1)),disabled:A<=1,children:[(0,W.jsx)(xe,{size:14}),` `,`Previous`]}),(0,W.jsx)(`span`,{className:`gift-pager-page`,children:`Page ${A} of ${D}`}),(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>f(e=>Math.min(D,e+1)),disabled:A>=D,children:[`Next`,` `,(0,W.jsx)(Se,{size:14})]})]})]}),v&&(0,W.jsx)(Er,{set:v,onClose:()=>y(null)}),b&&(0,W.jsx)(wr,{kind:e,onClose:()=>x(!1),onCreated:()=>void T()})]})}var kr=[`Love`,`Approval`,`Disapproval`,`Cheers`,`Laughter`,`Astonishment`,`Sadness`,`Anger`,`Neutral`,`Doubt`,`Silly`];function Ar({documentID:e}){let[t,n]=(0,g.useState)(!1);return t?(0,W.jsx)(`div`,{className:`sticker-list-thumb-empty`,children:(0,W.jsx)(Ie,{size:14})}):(0,W.jsx)(`video`,{className:`gif-catalog-thumb`,src:k.gifCatalogDocumentPreviewURL(e),muted:!0,loop:!0,autoPlay:!0,playsInline:!0,onError:()=>n(!0)})}function jr(){let[e,t]=(0,g.useState)([]),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(!1),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(10),[u,d]=(0,g.useState)(1),[f,p]=(0,g.useState)({}),[m,h]=(0,g.useState)({}),[_,v]=(0,g.useState)(!1);async function y(){a(!0),s(``);try{t((await k.gifCatalog()).rows??[])}catch(e){s(O(e))}finally{a(!1)}}(0,g.useEffect)(()=>{y()},[]);let b=(0,g.useMemo)(()=>{let t=n.trim().toLowerCase();return t?e.filter(e=>e.ID.includes(t)||e.Title.toLowerCase().includes(t)):e},[e,n]);(0,g.useEffect)(()=>{d(1)},[n,c]);let x=c===`all`?1:Math.max(1,Math.ceil(b.length/c)),S=Math.min(u,x),C=(0,g.useMemo)(()=>{if(c===`all`)return b;let e=(S-1)*c;return b.slice(e,e+c)},[b,S,c]),w=C.length===0?0:c===`all`?1:(S-1)*c+1,T=w===0?0:w+C.length-1,E=(0,g.useMemo)(()=>({total:e.length,enabled:e.filter(e=>e.Enabled).length,uncategorized:e.filter(e=>!e.Category).length}),[e]);return(0,W.jsxs)(G,{title:`GIFs`,eyebrow:`Curated GIFs served by @gif in the client's GIF picker (trending + search)`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn`,type:`button`,onClick:()=>y(),disabled:i,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]}),(0,W.jsx)(Z,{tone:`neutral`,label:`Auto-categorize`,path:`/api/actions/auto-categorize-gif-catalog`,payload:()=>({}),onDone:()=>void y()}),(0,W.jsx)(Z,{tone:`danger`,label:`Delete uncategorized`,path:`/api/actions/delete-uncategorized-gifs`,payload:()=>({}),onDone:()=>void y()}),(0,W.jsxs)(`button`,{className:`btn primary`,type:`button`,onClick:()=>v(!0),children:[(0,W.jsx)(Ye,{size:15}),` `,`Add GIF`]})]}),children:[o&&(0,W.jsx)(q,{children:o}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Total GIFs`,value:String(E.total)}),(0,W.jsx)(Y,{label:`Enabled`,value:String(E.enabled),tone:`good`}),(0,W.jsx)(Y,{label:`Uncategorized`,value:String(E.uncategorized),tone:E.uncategorized>0?`warn`:void 0})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`div`,{className:`toolbar`,children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:n,onChange:e=>r(e.target.value),placeholder:`Search ID or title`})]}),(0,W.jsxs)(`label`,{className:`gift-page-size`,children:[(0,W.jsx)(`span`,{children:`Per page`}),(0,W.jsxs)(`select`,{value:String(c),onChange:e=>l(e.target.value===`all`?`all`:Number(e.target.value)),children:[(0,W.jsx)(`option`,{value:`10`,children:`10`}),(0,W.jsx)(`option`,{value:`20`,children:`20`}),(0,W.jsx)(`option`,{value:`50`,children:`50`}),(0,W.jsx)(`option`,{value:`100`,children:`100`}),(0,W.jsx)(`option`,{value:`all`,children:`All`})]})]}),(0,W.jsx)(`span`,{className:`gift-list-summary`,children:`Showing ${b.length} of ${e.length}`})]})}),(0,W.jsx)(`div`,{className:`table-wrap gift-table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Preview`}),(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Title`}),(0,W.jsx)(`th`,{children:`Document ID`}),(0,W.jsx)(`th`,{children:`Added by`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Category`}),(0,W.jsx)(`th`,{children:`Sort order`}),(0,W.jsx)(`th`,{children:`Actions`})]})}),(0,W.jsxs)(`tbody`,{children:[C.map(e=>(0,W.jsxs)(`tr`,{className:e.Enabled?``:`gift-row-disabled`,children:[(0,W.jsx)(`td`,{children:(0,W.jsx)(Ar,{documentID:e.DocumentID})}),(0,W.jsx)(`td`,{className:`mono`,children:e.ID}),(0,W.jsx)(`td`,{children:e.Title||(0,W.jsx)(`span`,{className:`muted-cell`,children:`Untitled`})}),(0,W.jsx)(`td`,{className:`mono`,children:e.DocumentID}),(0,W.jsx)(`td`,{children:e.CreatedBy||(0,W.jsx)(`span`,{className:`muted-cell`,children:`—`})}),(0,W.jsx)(`td`,{children:e.Enabled?(0,W.jsx)(J,{tone:`good`,children:`Enabled`}):(0,W.jsx)(J,{tone:`danger`,children:`Disabled`})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`sort-order-editor`,children:[(0,W.jsxs)(`select`,{className:`small-input`,value:m[e.ID]??e.Category,onChange:t=>h(n=>({...n,[e.ID]:t.target.value})),children:[(0,W.jsx)(`option`,{value:``,children:`Uncategorized`}),kr.map(e=>(0,W.jsx)(`option`,{value:e,children:e},e))]}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:`Save`,path:`/api/actions/set-gif-catalog-category`,payload:()=>({id:e.ID,category:m[e.ID]??e.Category}),onDone:()=>void y()})]})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`sort-order-editor`,children:[(0,W.jsx)(`input`,{type:`number`,className:`small-input`,value:f[e.ID]??String(e.SortOrder),onChange:t=>p(n=>({...n,[e.ID]:t.target.value}))}),(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:`Save`,path:`/api/actions/set-gif-catalog-sort-order`,payload:()=>({id:e.ID,sort_order:Number(f[e.ID]??e.SortOrder)}),onDone:()=>void y()})]})}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`gift-table-actions`,children:[(0,W.jsx)(Z,{compact:!0,tone:`neutral`,label:e.Enabled?`Disable`:`Enable`,path:`/api/actions/set-gif-catalog-enabled`,payload:()=>({id:e.ID,enabled:!e.Enabled}),onDone:()=>void y()}),(0,W.jsx)(Z,{compact:!0,tone:`danger`,label:`Delete`,path:`/api/actions/delete-gif-catalog-entry`,payload:()=>({id:e.ID}),onDone:()=>void y()})]})})]},e.ID)),C.length===0&&(0,W.jsx)(Bt,{colSpan:9})]})]})}),c!==`all`&&b.length>0&&(0,W.jsxs)(`div`,{className:`gift-pager`,children:[(0,W.jsx)(`span`,{className:`gift-pager-range`,children:`Showing ${w}-${T} of ${b.length}`}),(0,W.jsxs)(`div`,{className:`gift-pager-controls`,children:[(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>d(e=>Math.max(1,e-1)),disabled:S<=1,children:[(0,W.jsx)(xe,{size:14}),` `,`Previous`]}),(0,W.jsx)(`span`,{className:`gift-pager-page`,children:`Page ${S} of ${x}`}),(0,W.jsxs)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>d(e=>Math.min(x,e+1)),disabled:S>=x,children:[`Next`,` `,(0,W.jsx)(Se,{size:14})]})]})]}),_&&(0,W.jsx)(Mr,{onClose:()=>v(!1),onCreated:()=>void y()})]})}function Mr({onClose:e,onCreated:t}){let[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(null),[o,s]=(0,g.useState)(null),[c,l]=(0,g.useState)(``),[u,d]=(0,g.useState)(!1),[f,p]=(0,g.useState)(``);function m(e){a(e),s(t=>(t&&URL.revokeObjectURL(t),e?URL.createObjectURL(e):null))}async function h(){if(!n.trim()||!i){p(`Title and a GIF/MP4 file are required.`);return}if(!c.trim()){p(`Please enter an operation reason`);return}d(!0),p(``);try{let r=new FormData;r.set(`metadata`,JSON.stringify({command_id:``,reason:c.trim(),confirm:!0,title:n.trim()})),r.set(`file`,i,i.name),await k.createGifCatalogEntry(r),t(),e()}catch(e){p(O(e))}finally{d(!1)}}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Add a GIF`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`New catalog entry`}),(0,W.jsx)(`h2`,{children:`Add a GIF`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:e,disabled:u,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsx)(`div`,{className:`gift-fields-grid`,children:(0,W.jsxs)(`label`,{children:[(0,W.jsx)(`span`,{children:`Title`}),(0,W.jsx)(`input`,{value:n,maxLength:128,onChange:e=>r(e.target.value)})]})}),(0,W.jsxs)(`label`,{className:`gift-file-picker ${i?`has-file`:``}`,children:[(0,W.jsx)(`input`,{type:`file`,accept:`.gif,.mp4,image/gif,video/mp4`,onChange:e=>m(e.target.files?.[0]??null)}),(0,W.jsxs)(`span`,{className:`gift-file-copy`,children:[(0,W.jsx)(`span`,{className:`gift-field-label`,children:`File`}),(0,W.jsx)(`strong`,{children:i?i.name:`Choose a GIF or MP4 file`})]}),(0,W.jsx)(`span`,{className:`gift-file-action`,children:i?`Change file`:`Choose file`})]}),o&&(0,W.jsx)(`div`,{className:`gif-catalog-preview`,children:i?.type===`video/mp4`?(0,W.jsx)(`video`,{src:o,autoPlay:!0,loop:!0,muted:!0,playsInline:!0}):(0,W.jsx)(`img`,{src:o,alt:``})}),(0,W.jsxs)(`label`,{className:`gift-reason-field`,children:[(0,W.jsx)(`span`,{children:`Audit reason`}),(0,W.jsx)(`input`,{value:c,placeholder:`Briefly describe why this GIF is being added`,onChange:e=>l(e.target.value)})]}),f&&(0,W.jsx)(q,{children:f})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:e,disabled:u,children:`Close`}),(0,W.jsxs)(`button`,{className:`btn primary`,type:`button`,onClick:h,disabled:u,children:[u?(0,W.jsx)(I,{className:`spin`,size:15}):(0,W.jsx)(ht,{size:15}),`Add GIF`]})]})]})}),document.body)}function Nr(){let[e,t]=(0,g.useState)(`settings`);return(0,W.jsxs)(G,{title:`Server Settings`,eyebrow:`Identity, .env, and live process/service control`,children:[(0,W.jsxs)(`div`,{className:`tab-bar`,role:`tablist`,"aria-label":`Server Settings sections`,children:[(0,W.jsx)(`button`,{className:`tab-btn ${e===`settings`?`active`:``}`,type:`button`,role:`tab`,"aria-selected":e===`settings`,onClick:()=>t(`settings`),children:`Settings`}),(0,W.jsx)(`button`,{className:`tab-btn ${e===`services`?`active`:``}`,type:`button`,role:`tab`,"aria-selected":e===`services`,onClick:()=>t(`services`),children:`Services`})]}),e===`settings`?(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsx)(Pr,{}),(0,W.jsx)(Fr,{}),(0,W.jsx)(Lr,{})]}):(0,W.jsx)(`div`,{className:`stacked-sections`,children:(0,W.jsx)(Jr,{})})]})}function Pr(){let[e,t]=(0,g.useState)(null),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(!1),[c,l]=(0,g.useState)(0),[u,d]=(0,g.useState)(!1),[f,p]=(0,g.useState)(``);async function m(){p(``);try{let e=await k.serverIdentity();t(e),r(e.name),a(e.description),d(!1)}catch(e){p(O(e))}}return(0,g.useEffect)(()=>{m()},[]),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Server identity`}),f&&(0,W.jsx)(q,{children:f}),e?(0,W.jsxs)(`div`,{className:`card-body identity-card`,children:[(0,W.jsxs)(`div`,{className:`identity-layout`,children:[(0,W.jsxs)(`div`,{className:`avatar-edit-slot`,children:[e.icon_ext&&!u?(0,W.jsx)(`img`,{className:`avatar-photo-img`,src:k.serverIconURL()+`&b=${c}`,alt:``,style:{width:88,height:88},onError:()=>d(!0)}):(0,W.jsx)(`div`,{className:`avatar-fallback server-icon-fallback`,style:{width:88,height:88},children:(0,W.jsx)(Ie,{size:26})}),(0,W.jsx)(`button`,{className:`icon-btn avatar-edit-btn`,type:`button`,"aria-label":`Change server icon`,title:`Change server icon`,onClick:()=>s(!0),children:(0,W.jsx)(Le,{size:14})})]}),(0,W.jsxs)(`div`,{className:`server-identity-fields`,children:[(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsx)(`span`,{children:`Name`}),(0,W.jsx)(`input`,{value:n,maxLength:128,onChange:e=>r(e.target.value)})]}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsx)(`span`,{children:`Description`}),(0,W.jsx)(`textarea`,{rows:4,value:i,maxLength:512,onChange:e=>a(e.target.value)})]})]})]}),(0,W.jsx)(`div`,{className:`gift-table-actions identity-save-row`,children:(0,W.jsx)(Z,{tone:`neutral`,label:`Save identity`,path:`/api/actions/set-server-identity`,payload:()=>({name:n,description:i}),onDone:()=>void m()})})]}):(0,W.jsx)(Vt,{label:`Loading identity...`}),o&&(0,W.jsx)(Ir,{hasIcon:!!e?.icon_ext,onClose:()=>s(!1),onDone:()=>{l(e=>e+1),d(!1),m()}})]})}function Fr(){let[e,t]=(0,g.useState)(null),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(``);async function u(){l(``);try{let e=await k.serverIdentity();t(e),r(e.welcome_message_phone_template??``),a(e.welcome_message_email_template??``),s(e.login_code_message_template??``)}catch(e){l(O(e))}}(0,g.useEffect)(()=>{u()},[]);let d=n.trim()!==``,f=i.trim()!==``,p=o.trim()!==``,m=(o.match(/\{\{code\}\}/g)??[]).length,h=p&&m!==1;return(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Login notifications`}),c&&(0,W.jsx)(q,{children:c}),e?(0,W.jsxs)(`div`,{className:`card-body`,children:[(0,W.jsxs)(`p`,{style:{color:`var(--muted)`,marginTop:0},children:[`Sent from the official system account on every completed sign-in. Use `,(0,W.jsx)(`code`,{children:`{{server_name}}`}),` to insert the server's configured name.`]}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsxs)(`span`,{children:[`Phone sign-in template`,` `,d?(0,W.jsx)(`span`,{className:`badge good`,children:`custom`}):(0,W.jsx)(`span`,{className:`badge`,children:`default`})]}),(0,W.jsx)(`textarea`,{rows:4,value:n,onChange:e=>r(e.target.value),placeholder:e.default_welcome_message_phone_template})]}),(0,W.jsx)(`div`,{className:`gift-table-actions`,children:(0,W.jsx)(Z,{tone:`neutral`,compact:!0,label:`Reset to default`,path:`/api/actions/set-welcome-message-templates`,payload:()=>({phone_template:``,email_template:i}),disabled:!d,onDone:()=>{r(``),u()}})}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsxs)(`span`,{children:[`Email sign-in template`,` `,f?(0,W.jsx)(`span`,{className:`badge good`,children:`custom`}):(0,W.jsx)(`span`,{className:`badge`,children:`default`})]}),(0,W.jsx)(`textarea`,{rows:4,value:i,onChange:e=>a(e.target.value),placeholder:e.default_welcome_message_email_template})]}),(0,W.jsx)(`div`,{className:`gift-table-actions`,children:(0,W.jsx)(Z,{tone:`neutral`,compact:!0,label:`Reset to default`,path:`/api/actions/set-welcome-message-templates`,payload:()=>({phone_template:n,email_template:``}),disabled:!f,onDone:()=>{a(``),u()}})}),(0,W.jsx)(`div`,{className:`gift-table-actions identity-save-row`,children:(0,W.jsx)(Z,{tone:`neutral`,label:`Save login notification templates`,path:`/api/actions/set-welcome-message-templates`,payload:()=>({phone_template:n,email_template:i}),onDone:()=>void u()})}),(0,W.jsxs)(`p`,{style:{color:`var(--muted)`,marginTop:`1.5em`,borderTop:`1px solid var(--line)`,paddingTop:`1em`},children:[`Sent from the official system account with every login code (SMS and email alike). Must contain `,(0,W.jsx)(`code`,{children:`{{code}}`}),` exactly once -- that's where the actual code is inserted and bolded. `,(0,W.jsx)(`code`,{children:`{{server_name}}`}),` is optional and may appear any number of times.`]}),(0,W.jsxs)(`label`,{className:`form-field`,children:[(0,W.jsxs)(`span`,{children:[`Login-code message template`,` `,p?(0,W.jsx)(`span`,{className:`badge good`,children:`custom`}):(0,W.jsx)(`span`,{className:`badge`,children:`default`})]}),(0,W.jsx)(`textarea`,{rows:5,value:o,onChange:e=>s(e.target.value),placeholder:e.default_login_code_message_template}),h&&(0,W.jsx)(`span`,{style:{color:`var(--danger-text)`,fontSize:`0.85em`},children:m===0?`Must contain {{code}} exactly once -- it is currently missing.`:`Must contain {{code}} exactly once -- it currently appears ${m} times.`})]}),(0,W.jsx)(`div`,{className:`gift-table-actions`,children:(0,W.jsx)(Z,{tone:`neutral`,compact:!0,label:`Reset to default`,path:`/api/actions/set-login-code-message-template`,payload:()=>({template:``}),disabled:!p,onDone:()=>{s(``),u()}})}),(0,W.jsx)(`div`,{className:`gift-table-actions identity-save-row`,children:(0,W.jsx)(Z,{tone:`neutral`,label:`Save login-code message template`,path:`/api/actions/set-login-code-message-template`,payload:()=>({template:o}),disabled:h,onDone:()=>void u()})})]}):(0,W.jsx)(Vt,{label:`Loading login notification templates...`})]})}function Ir({hasIcon:e,onClose:t,onDone:n}){let[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(!1),[d,f]=(0,g.useState)(``);(0,g.useEffect)(()=>{if(!r){o(``);return}let e=URL.createObjectURL(r);return o(e),()=>URL.revokeObjectURL(e)},[r]);async function p(){if(!r){f(`Choose an image file first.`);return}if(!s.trim()){f(`Please enter an operation reason`);return}u(!0),f(``);try{let e=new FormData;e.set(`metadata`,JSON.stringify({command_id:``,reason:s.trim(),confirm:!0})),e.set(`file`,r,r.name);let i=await k.uploadServerIcon(e);if(i.error){f(i.error);return}n(),t()}catch(e){f(O(e))}finally{u(!1)}}async function m(){if(!s.trim()){f(`Please enter an operation reason`);return}u(!0),f(``);try{let e=await k.action(`/api/actions/remove-server-icon`,{command_id:``,reason:s.trim(),confirm:!0});if(e.error){f(e.error);return}n(),t()}catch(e){f(O(e))}finally{u(!1)}}return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Change server icon`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Server identity`}),(0,W.jsx)(`h2`,{children:`Change server icon`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:t,disabled:l,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsxs)(`label`,{className:`gift-file-picker ${r?`has-file`:``}`,children:[(0,W.jsx)(`input`,{type:`file`,accept:`.png,.jpg,.jpeg,.webp,.gif,image/png,image/jpeg,image/webp,image/gif`,onChange:e=>i(e.target.files?.[0]??null)}),a?(0,W.jsx)(`img`,{className:`gift-file-icon`,src:a,alt:``,style:{objectFit:`cover`}}):(0,W.jsx)(Le,{size:22}),(0,W.jsxs)(`span`,{className:`gift-file-copy`,children:[(0,W.jsx)(`span`,{className:`gift-field-label`,children:`New icon`}),(0,W.jsx)(`strong`,{children:r?r.name:`Choose a PNG, JPEG, WebP, or GIF image`})]}),(0,W.jsx)(`span`,{className:`gift-file-action`,children:r?`Change file`:`Choose file`})]}),(0,W.jsxs)(`label`,{className:`gift-reason-field`,children:[(0,W.jsx)(`span`,{children:`Audit reason`}),(0,W.jsx)(`input`,{value:s,placeholder:`Briefly describe why the server icon is changing`,onChange:e=>c(e.target.value)})]}),d&&(0,W.jsx)(q,{children:d})]}),(0,W.jsxs)(`div`,{className:`modal-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:t,disabled:l,children:`Close`}),e&&(0,W.jsxs)(`button`,{className:`btn danger icon-text`,type:`button`,onClick:()=>void m(),disabled:l,children:[l?(0,W.jsx)(I,{className:`spin`,size:15}):(0,W.jsx)(pt,{size:15}),`Remove icon`]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`button`,onClick:()=>void p(),disabled:l,children:[l?(0,W.jsx)(I,{className:`spin`,size:15}):(0,W.jsx)(ht,{size:15}),`Upload icon`]})]})]})}),document.body)}function Lr(){let[e,t]=(0,g.useState)([]),[n,r]=(0,g.useState)({}),[i,a]=(0,g.useState)({}),[o,s]=(0,g.useState)(``);async function c(){s(``);try{let e=await k.serverEnv();t(e);let n={};for(let t of e)for(let e of t.fields)n[e.key]=e.value;r(n)}catch(e){s(O(e))}}return(0,g.useEffect)(()=>{c()},[]),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Environment (.env)`,text:`${(0,g.useMemo)(()=>e.reduce((e,t)=>e+t.fields.length,0),[e])} setting(s) across ${e.length} group(s). Changes take effect on the next Restart/Update.`}),o&&(0,W.jsx)(q,{children:o}),(0,W.jsx)(`div`,{className:`env-groups`,children:e.map(e=>{let t=!!i[e.title];return(0,W.jsxs)(`div`,{className:`env-group ${t?`open`:``}`,children:[(0,W.jsxs)(`button`,{className:`env-group-toggle`,type:`button`,"aria-expanded":t,onClick:()=>a(t=>({...t,[e.title]:!t[e.title]})),children:[(0,W.jsxs)(`span`,{className:`env-group-toggle-text`,children:[(0,W.jsx)(`span`,{className:`env-group-toggle-title`,children:e.title}),(0,W.jsx)(`span`,{className:`env-group-toggle-count`,children:`${e.fields.length} field${e.fields.length===1?``:`s`}`})]}),(0,W.jsx)(be,{size:16,className:`env-group-chevron`})]}),t&&(0,W.jsxs)(`div`,{className:`env-group-body`,children:[e.description&&(0,W.jsx)(`p`,{className:`env-group-desc`,children:e.description}),e.fields.map(e=>(0,W.jsxs)(`label`,{className:`form-field env-field`,children:[(0,W.jsx)(`span`,{className:`mono`,children:e.key}),e.description&&(0,W.jsx)(`span`,{className:`env-field-desc`,children:e.description}),(0,W.jsx)(`input`,{type:e.sensitive?`password`:`text`,value:n[e.key]??``,placeholder:e.default_value,onChange:t=>r(n=>({...n,[e.key]:t.target.value}))})]},e.key))]})]},e.title)})}),(0,W.jsx)(`div`,{className:`gift-table-actions env-save-row`,children:(0,W.jsx)(Z,{tone:`warn`,label:`Save .env changes`,path:`/api/actions/update-server-env`,payload:()=>({values:n}),onDone:()=>void c()})})]})}function Rr(e){return new Promise(t=>setTimeout(t,e))}function zr(){let[e,t]=(0,g.useState)(!1),[n,r]=(0,g.useState)(!1),i=(0,g.useRef)(!1);return{waiting:e,timedOut:n,watch:(0,g.useCallback)(async(e=15e4)=>{i.current=!1,r(!1),t(!0);let n=``;try{n=(await k.session()).boot_id??``}catch{}let a=Date.now()+e;for(;Date.now(){i.current=!0,t(!1),r(!1)},[])}}function Br({label:e,timedOut:t,onDismiss:n}){return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsx)(`section`,{className:`modal command-modal restart-overlay`,role:`dialog`,"aria-modal":`true`,"aria-label":e,children:t?(0,W.jsxs)(`div`,{className:`command-body restart-overlay-body`,children:[(0,W.jsx)(q,{children:`The admin panel did not come back within the expected time. It may still be building/restarting -- reload manually in a bit, or check the server logs.`}),(0,W.jsxs)(`div`,{className:`gift-table-actions restart-overlay-actions`,children:[(0,W.jsx)(`button`,{className:`btn`,type:`button`,onClick:n,children:`Dismiss`}),(0,W.jsx)(`button`,{className:`btn primary`,type:`button`,onClick:()=>window.location.reload(),children:`Reload now`})]})]}):(0,W.jsxs)(`div`,{className:`command-body restart-overlay-body`,children:[(0,W.jsx)(I,{className:`spin`,size:28}),(0,W.jsx)(`p`,{children:e})]})})}),document.body)}function Vr(e){switch(e){case`good`:return(0,W.jsx)(F,{size:15});case`warn`:return(0,W.jsx)(I,{className:`spin`,size:15});case`danger`:return(0,W.jsx)(ne,{size:15});default:return(0,W.jsx)(Ce,{size:15})}}function Hr({icon:e,name:t,tone:n,statusLabel:r,detail:i}){return(0,W.jsxs)(`div`,{className:`service-card tone-${n}`,children:[(0,W.jsx)(`div`,{className:`service-card-icon`,children:e}),(0,W.jsxs)(`div`,{className:`service-card-body`,children:[(0,W.jsx)(`div`,{className:`service-card-name`,children:t}),(0,W.jsx)(`div`,{className:`service-card-detail`,children:i??`\xA0`})]}),(0,W.jsxs)(`div`,{className:`service-card-status`,children:[Vr(n),(0,W.jsx)(`span`,{children:r})]})]})}var Ur={postgres:(0,W.jsx)(Te,{size:18}),redis:(0,W.jsx)(re,{size:18}),minio:(0,W.jsx)(Pe,{size:18})};function Wr(e){let t=e.state.toLowerCase(),n=e.health.toLowerCase();return t!==`running`||n===`unhealthy`?`danger`:n===`starting`?`warn`:`good`}function Gr(e){return e.state.toLowerCase()===`running`?e.health?e.health:`running`:e.state||`stopped`}var Kr=4e3;function qr({onUpdateStarted:e}){let[t,n]=(0,g.useState)(null),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(!1),[s,c]=(0,g.useState)(``),l=(0,g.useCallback)(async()=>{o(!0),c(``),i(``);try{let e=await k.checkServerUpdates();n(e.commits_behind),i(e.commits_behind>0?`${e.commits_behind} new commit${e.commits_behind===1?``:`s`} pulled from GitHub.`:`Already up to date.`)}catch(e){c(O(e))}finally{o(!1)}},[]);(0,g.useEffect)(()=>{l()},[l]);async function u(){let n=t??0;if(window.confirm(`Pull ${n} commit(s), rebuild, and restart owpengram-server and the admin panel?`)){o(!0),c(``);try{let t=await k.action(`/api/actions/update-server`,{command_id:``,reason:`Update via Services tab`,confirm:!0});if(t.error){c(t.error),o(!1);return}e(`Pulled ${n} commit${n===1?``:`s`} -- rebuilding and restarting owpengram-server and the admin panel...`)}catch(e){c(O(e)),o(!1)}}}let d=(t??0)>0;return(0,W.jsxs)(`button`,{className:`btn compact-btn icon-text ${d?`danger`:``}`,type:`button`,disabled:a,title:s||r||void 0,onClick:()=>void(d?u():l()),children:[a?(0,W.jsx)(I,{className:`spin`,size:15}):d?(0,W.jsx)(Ee,{size:15}):(0,W.jsx)(B,{size:15}),d?`Update (${t})`:`Check updates`]})}function Jr(){let[e,t]=(0,g.useState)(null),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)(null),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(``),u=zr(),d=(0,g.useRef)(!1);d.current=u.waiting;let f=(0,g.useCallback)(async()=>{if(!d.current){try{t(await k.serverStatus()),r(``)}catch(e){r(O(e))}try{a(await k.dockerStatus()),s(``)}catch(e){s(O(e))}}},[]);return(0,g.useEffect)(()=>{f();let e=window.setInterval(()=>void f(),Kr);return()=>window.clearInterval(e)},[f]),(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Services`,action:(0,W.jsxs)(`div`,{className:`services-header-actions`,children:[(0,W.jsx)(qr,{onUpdateStarted:e=>{l(e),u.watch()}}),(0,W.jsx)(Z,{compact:!0,tone:`primary`,label:`Restart`,path:`/api/actions/restart-server`,payload:()=>({}),onDone:()=>{l(`Restarting owpengram-server and the admin panel...`),u.watch()}})]})}),n&&(0,W.jsx)(q,{children:n}),o&&(0,W.jsx)(q,{children:o}),e===null&&i===null&&!n&&!o?(0,W.jsx)(Vt,{label:`Loading service status...`}):(0,W.jsxs)(`div`,{className:`service-grid`,children:[e&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(Hr,{icon:(0,W.jsx)(tt,{size:18}),name:`Server`,tone:e.ServerAlive?`good`:`danger`,statusLabel:e.ServerAlive?`running`:`stopped`,detail:e.ServerAlive?`pid ${e.ServerPID}`:void 0}),(0,W.jsx)(Hr,{icon:(0,W.jsx)(ot,{size:18}),name:`admin panel`,tone:e.AdminAlive?`good`:`danger`,statusLabel:e.AdminAlive?`running`:`stopped`,detail:e.AdminAlive?`pid ${e.AdminPID}`:void 0})]}),i?.map(e=>(0,W.jsx)(Hr,{icon:Ur[e.name]??(0,W.jsx)(Te,{size:18}),name:e.name,tone:Wr(e),statusLabel:Gr(e),detail:e.state},e.name))]})]}),u.waiting&&(0,W.jsx)(Br,{label:c,timedOut:!1,onDismiss:u.dismiss}),u.timedOut&&(0,W.jsx)(Br,{label:c,timedOut:!0,onDismiss:u.dismiss})]})}var Yr=`open,in_review,action_pending,action_failed,appeal_review`,Xr=[{value:Yr,label:`Active queue`},{value:`open,in_review,action_pending,action_failed,resolved,dismissed,appeal_review`,label:`All statuses`},{value:`open`,label:`Open`},{value:`in_review`,label:`In review`},{value:`action_pending`,label:`Action pending`},{value:`action_failed`,label:`Action failed`},{value:`appeal_review`,label:`Appeal review`},{value:`resolved`,label:`Resolved`},{value:`dismissed`,label:`Dismissed`}];function Zr({navigate:e}){let[t,n]=(0,g.useState)(Yr),[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)([]),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(``);async function d(){c(!0),u(``);try{let e=new URLSearchParams({statuses:t,limit:`100`});r.trim()&&e.set(`assigned_to`,r.trim()),o((await k.moderationCases(e)).cases)}catch(e){u(O(e))}finally{c(!1)}}(0,g.useEffect)(()=>{d()},[]);let f=a.filter(e=>e.Status===`action_pending`||e.Status===`action_failed`).length,p=a.filter(e=>e.Severity===4).length;return(0,W.jsxs)(G,{title:`Reports and Moderation`,eyebrow:`Moderation / Cases`,actions:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:d,disabled:s,children:[(0,W.jsx)(B,{size:15,className:s?`spin`:``}),` `,`Refresh`]}),children:[l&&(0,W.jsx)(q,{children:l}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Current queue`,value:String(a.length)}),(0,W.jsx)(Y,{label:`Critical cases`,value:String(p),tone:p?`danger`:`neutral`}),(0,W.jsx)(Y,{label:`Pending / failed actions`,value:String(f),tone:f?`warn`:`good`})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),d()},children:[(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Status`}),(0,W.jsx)(`select`,{"aria-label":`Case status filter`,value:t,onChange:e=>n(e.target.value),children:Xr.map(e=>(0,W.jsx)(`option`,{value:e.value,children:e.label},e.value))})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Reviewer`}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`Leave blank for all`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:s,children:[(0,W.jsx)(at,{size:15}),` `,`Search`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Case`}),(0,W.jsx)(`th`,{children:`Target`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Severity`}),(0,W.jsx)(`th`,{children:`Reports / Reporters`}),(0,W.jsx)(`th`,{children:`Reviewer`}),(0,W.jsx)(`th`,{children:`Latest report`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[a.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsxs)(`td`,{className:`mono`,children:[`#`,t.ID]}),(0,W.jsx)(`td`,{className:`mono`,children:ri(t.Target.Type,t.Target.ID)}),(0,W.jsx)(`td`,{children:(0,W.jsx)(Qr,{status:t.Status})}),(0,W.jsx)(`td`,{children:(0,W.jsx)(ei,{value:t.Severity})}),(0,W.jsxs)(`td`,{children:[t.ReportCount,` / `,t.DistinctReporterCount]}),(0,W.jsx)(`td`,{children:t.AssignedTo||`-`}),(0,W.jsx)(`td`,{children:U(t.LastReportAt)}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,onClick:()=>e(`/moderation/${t.ID}`),children:[`Review`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),a.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})})]})}function Qr({status:e}){return(0,W.jsx)(J,{tone:e===`resolved`||e===`dismissed`?`good`:e===`action_failed`?`danger`:e===`action_pending`?`warn`:`neutral`,children:ni(`status`,e)})}var $r={low:`Low`,medium:`Medium`,high:`High`,critical:`Critical`};function ei({value:e}){let t=[``,`low`,`medium`,`high`,`critical`][e];return(0,W.jsx)(J,{tone:e>=4?`danger`:e>=3?`warn`:`neutral`,children:t?$r[t]:e})}var ti={status:{open:`Open`,in_review:`In review`,action_pending:`Action pending`,action_failed:`Action failed`,appeal_review:`Appeal review`,resolved:`Resolved`,dismissed:`Dismissed`},targetType:{channel:`Channel`,chat:`Group`,user:`Account`},source:{account_peer:`Account / peer`,antispam_false_positive:`Anti-spam false positive`,channel_spam:`Channel spam`,encrypted_spam:`Encrypted-chat spam`,ephemeral:`Ephemeral media`,messages:`Messages`,messages_spam:`Message spam`,profile_photo:`Profile photo`,reaction:`Reaction`,sponsored:`Sponsored message`,story:`Story`},reason:{child_abuse:`Child abuse`,copyright:`Copyright`,fake:`Fake`,geo_irrelevant:`Location-irrelevant`,illegal_drugs:`Illegal drugs`,other:`Other`,personal_details:`Personal details`,pornography:`Pornography`,spam:`Spam`,violence:`Violence`}};function ni(e,t){return ti[e]?.[t]??t}function ri(e,t){return`${ni(`targetType`,e)} #${t}`}function ii({id:e,navigate:t}){let[n,r]=(0,g.useState)(null),[i,a]=(0,g.useState)(null),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(`no_violation`),[u,d]=(0,g.useState)(``),[f,p]=(0,g.useState)(``),[m,h]=(0,g.useState)(!0),[_,v]=(0,g.useState)(!1),[y,b]=(0,g.useState)(``);function x(e){a(e),e&&(d(e.Items.filter(e=>e.Kind===`message`).map(e=>Number(e.ItemID)).filter(e=>Number.isSafeInteger(e)&&e>0).join(`, `)),p(String(e.ReporterUserID)))}async function S(){b(``);try{let t=await k.moderationCase(e);r(t);let n=t.ReportIDs[0];x(n?await k.moderationReport(n):null)}catch(e){b(O(e))}}(0,g.useEffect)(()=>{S()},[e]);let C=(0,g.useMemo)(()=>ai(c,n?.Case.Target.Type,oi(u),Number(f),m),[c,n?.Case.Target.Type,u,f,m]),w=(0,g.useMemo)(()=>n?si(n):{actions:[],label:`None`,blocked:!1},[n]);async function T(){if(n){v(!0),b(``);try{await k.claimModerationCase(e,n.Case.Version),await S()}catch(e){b(O(e))}finally{v(!1)}}}async function E(){if(!n||!o.trim()){b(`A review reason is required.`);return}if(c===`delete_messages`&&C.length===0){b(n.Case.Target.Type===`user`?`Private-message deletion requires valid evidence message IDs and the reporter's owner_user_id.`:`Channel-message deletion requires at least one valid evidence message ID.`);return}if(window.confirm(`Submit the “${ci(c)}” decision? The action will run through the durable action queue.`)){v(!0),b(``);try{r((await k.decideModerationCase(e,{expected_version:n.Case.Version,reason:o.trim(),kind:c===`no_violation`?`no_violation`:`violation`,actions:C})).case),s(``)}catch(e){b(O(e))}finally{v(!1)}}}async function D(t,i){if(!n||!o.trim()){b(`An appeal review reason is required.`);return}if(window.confirm(i?`Grant this appeal?`:`Deny this appeal?`)){v(!0);try{r((await k.reviewModerationAppeal(e,t,{expected_version:n.Case.Version,reason:o.trim(),granted:i,actions:i?w.actions:[]})).case),s(``)}catch(e){b(O(e))}finally{v(!1)}}}if(y&&!n)return(0,W.jsx)(q,{children:y});if(!n)return(0,W.jsx)(Vt,{label:`Loading moderation case…`});let A=n.Case,j=A.Status===`open`||A.Status===`in_review`||A.Status===`appeal_review`,M=(A.Status===`in_review`||A.Status===`action_failed`)&&!!A.AssignedTo,N=M&&(A.Status!==`action_failed`||c!==`no_violation`),P=n.Appeals.find(e=>e.Status===`pending`);return(0,W.jsxs)(G,{title:`Review case #${A.ID}`,eyebrow:`Moderation / Case detail`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,onClick:()=>t(`/moderation`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to queue`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,onClick:S,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]})]}),children:[y&&(0,W.jsx)(q,{children:y}),(0,W.jsx)(Rt,{main:(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:ri(A.Target.Type,A.Target.ID)}),(0,W.jsx)(`div`,{className:`entity-subtitle`,children:`Version ${A.Version} · Updated ${U(A.UpdatedAt)}`})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[(0,W.jsx)(Qr,{status:A.Status}),(0,W.jsx)(ei,{value:A.Severity})]})]}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Target`,value:ri(A.Target.Type,A.Target.ID),mono:!0}),(0,W.jsx)(X,{label:`Reports`,value:`${A.ReportCount} reports from ${A.DistinctReporterCount} reporters`}),(0,W.jsx)(X,{label:`Reviewer`,value:A.AssignedTo||`-`}),(0,W.jsx)(X,{label:`First / latest report`,value:`${U(A.FirstReportAt)} / ${U(A.LastReportAt)}`})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Report evidence`,text:`Shows up to the latest 100 reports; snapshots are frozen when reports are admitted.`}),(0,W.jsx)(`div`,{className:`toolbar`,children:n.ReportIDs.map(e=>(0,W.jsxs)(`button`,{className:`btn`,onClick:async()=>x(await k.moderationReport(e)),children:[`#`,e]},e))}),i&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Source / Reason`,value:`${ni(`source`,i.Source)} / ${ni(`reason`,i.Reason)}`}),(0,W.jsx)(X,{label:`Reporter`,value:String(i.ReporterUserID),mono:!0}),(0,W.jsx)(X,{label:`Option`,value:i.Option,mono:!0}),(0,W.jsx)(X,{label:`Time`,value:U(i.CreatedAt)})]}),i.Comment&&(0,W.jsx)(`p`,{className:`about-text`,children:i.Comment}),(0,W.jsx)(Ht,{value:JSON.stringify(i,null,2)})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Decision and action audit`,text:`Actions run idempotently through a lease worker; failures retain their error and attempt count.`}),(0,W.jsx)(Ht,{value:JSON.stringify({decisions:n.Decisions,actions:n.Actions},null,2)})]}),n.Appeals.length>0&&(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Appeals`}),(0,W.jsx)(Ht,{value:JSON.stringify(n.Appeals,null,2)})]})]}),side:(0,W.jsxs)(`section`,{className:`action-dock`,children:[(0,W.jsx)(`div`,{className:`dock-title`,children:`Case actions`}),j&&(0,W.jsxs)(`button`,{className:`btn primary icon-text`,disabled:_,onClick:T,children:[(0,W.jsx)(ot,{size:15}),` `,A.AssignedTo?`Renew claim`:`Claim case`]}),(0,W.jsxs)(`label`,{className:`field`,children:[(0,W.jsx)(`span`,{children:`Review reason`}),(0,W.jsx)(`textarea`,{value:o,onChange:e=>s(e.target.value),rows:5})]}),(0,W.jsxs)(`label`,{className:`field`,children:[(0,W.jsx)(`span`,{children:`Decision template`}),(0,W.jsxs)(`select`,{value:c,onChange:e=>l(e.target.value),children:[(0,W.jsx)(`option`,{value:`no_violation`,children:`No violation (dismiss report)`}),(0,W.jsx)(`option`,{value:`scam`,children:`Mark as SCAM`}),(0,W.jsx)(`option`,{value:`fake`,children:`Mark as FAKE`}),(0,W.jsx)(`option`,{value:`freeze`,children:`Freeze account`}),(0,W.jsx)(`option`,{value:`scam_freeze`,children:`SCAM + freeze`}),(0,W.jsx)(`option`,{value:`fake_freeze`,children:`FAKE + freeze`}),(0,W.jsx)(`option`,{value:`delete_messages`,children:`Delete messages covered by evidence`}),(0,W.jsx)(`option`,{value:`delete_account`,children:`Delete account`})]})]}),c===`delete_messages`&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`label`,{className:`field`,children:[(0,W.jsx)(`span`,{children:`Evidence message IDs (comma-separated)`}),(0,W.jsx)(`input`,{value:u,onChange:e=>d(e.target.value),placeholder:`101, 102`})]}),A.Target.Type===`user`&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`label`,{className:`field`,children:[(0,W.jsx)(`span`,{children:`Private-chat owner_user_id`}),(0,W.jsx)(`input`,{value:f,onChange:e=>p(e.target.value),inputMode:`numeric`})]}),(0,W.jsxs)(`label`,{className:`field checkbox-field`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:m,onChange:e=>h(e.target.checked)}),(0,W.jsx)(`span`,{children:`Revoke for both sides`})]})]}),(0,W.jsx)(q,{children:`The server will verify again that every message ID exists in this case's immutable report evidence.`})]}),A.Status===`action_failed`&&c===`no_violation`&&(0,W.jsx)(q,{children:`The action was partially executed and cannot be changed directly to no violation. Select a new action to retry while retaining the previous failure audit.`}),M&&(0,W.jsxs)(`button`,{className:`btn danger icon-text`,disabled:_||!N,onClick:E,children:[(0,W.jsx)(F,{size:15}),` `,A.Status===`action_failed`?`Retry action`:`Submit decision`]}),P&&A.AssignedTo&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`div`,{className:`dock-title`,children:`Appeal review #${P.ID}`}),(0,W.jsx)(X,{label:`Automatic remedy after approval`,value:w.label}),w.blocked&&(0,W.jsx)(q,{children:`The case contains a completed irreversible deletion. It cannot be marked as approved and restored; deny it or escalate for manual handling.`}),(0,W.jsx)(`button`,{className:`btn`,disabled:_,onClick:()=>D(P.ID,!1),children:`Deny appeal`}),(0,W.jsx)(`button`,{className:`btn primary`,disabled:_||w.blocked,onClick:()=>D(P.ID,!0),children:`Grant appeal`})]})]})})]})}function ai(e,t,n,r,i){switch(e){case`scam`:return[{kind:`mark_scam`,payload:{}}];case`fake`:return[{kind:`mark_fake`,payload:{}}];case`freeze`:return[{kind:`freeze_account`,payload:{}}];case`scam_freeze`:return[{kind:`mark_scam`,payload:{}},{kind:`freeze_account`,payload:{}}];case`fake_freeze`:return[{kind:`mark_fake`,payload:{}},{kind:`freeze_account`,payload:{}}];case`delete_messages`:return n.length===0?[]:t===`channel`?[{kind:`delete_channel_message`,payload:{ids:n}}]:t===`user`&&Number.isSafeInteger(r)&&r>0?[{kind:`delete_private_message`,payload:{owner_user_id:r,ids:n,revoke:i}}]:[];case`delete_account`:return[{kind:`delete_account`,payload:{}}];default:return[]}}function oi(e){let t=e.split(/[,\s]+/).filter(Boolean).map(Number);return t.length===0||t.some(e=>!Number.isSafeInteger(e)||e<=0)?[]:[...new Set(t)]}function si(e){let t=!1,n=!1,r=!1;for(let i of[...e.Actions].sort((e,t)=>e.ID-t.ID))if(i.Status===`succeeded`)switch(i.Kind){case`mark_scam`:case`mark_fake`:t=!0;break;case`clear_peer_flags`:t=!1;break;case`freeze_account`:n=!0;break;case`unfreeze_account`:n=!1;break;case`delete_private_message`:case`delete_channel_message`:case`delete_account`:r=!0;break}let i=[],a=[];return t&&(i.push({kind:`clear_peer_flags`,payload:{}}),a.push(`Clear SCAM / FAKE`)),n&&(i.push({kind:`unfreeze_account`,payload:{}}),a.push(`Unfreeze account`)),{actions:i,label:a.join(` + `)||`No recovery action needed`,blocked:r}}function ci(e){return{no_violation:`No violation (dismiss report)`,scam:`Mark as SCAM`,fake:`Mark as FAKE`,freeze:`Freeze account`,scam_freeze:`SCAM + freeze`,fake_freeze:`FAKE + freeze`,delete_messages:`Delete messages covered by evidence`,delete_account:`Delete account`}[e]}function li({label:e,sortKey:t,activeKey:n,desc:r,onSort:i}){return(0,W.jsx)(`th`,{children:(0,W.jsxs)(`button`,{type:`button`,className:`sort-header`,onClick:()=>i(t),children:[e,t===n?r?(0,W.jsx)(ue,{size:13}):(0,W.jsx)(pe,{size:13}):(0,W.jsx)(L,{size:13,className:`sort-header-idle`})]})})}function ui(){let[e,t]=(0,g.useState)(null),[n,r]=(0,g.useState)([]),[i,a]=(0,g.useState)(!1),[o,s]=(0,g.useState)(0),[c,l]=(0,g.useState)(!1),[u,d]=(0,g.useState)(``),[f,p]=(0,g.useState)(``),[m,h]=(0,g.useState)(`bytes`),[_,v]=(0,g.useState)(!0);async function y(){try{t(await k.storageStats())}catch{}}async function b(e=!1){l(!0),d(``);let t=new URLSearchParams({limit:`50`,offset:String(e?o:0),sort:m,order:_?`desc`:`asc`});f.trim()&&t.set(`q`,f.trim());try{let n=await k.storageAccounts(t),i=n.rows??[];r(t=>e?[...t,...i]:i),s(n.next_offset),a(!!n.has_more)}catch(e){d(O(e))}finally{l(!1)}}function x(){y(),b(!1)}(0,g.useEffect)(()=>{x()},[m,_]);function S(e){e===m?v(e=>!e):(h(e),v(!0))}let C=e?Math.max(0,Number(e.LogicalBytes)-Number(e.PhysicalBytes)):0;return(0,W.jsxs)(W.Fragment,{children:[u&&(0,W.jsx)(q,{children:u}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Physical usage (on disk / S3)`,value:e?Pt(e.PhysicalBytes):`-`}),(0,W.jsx)(Y,{label:`Logical usage (sum per account)`,value:e?Pt(e.LogicalBytes):`-`}),(0,W.jsx)(Y,{label:`Saved by dedup`,value:Pt(String(C)),tone:C>0?`good`:`neutral`}),(0,W.jsx)(Y,{label:`Backend`,value:e?.BackendKind??`-`})]}),(0,W.jsxs)(`div`,{className:`metric-row`,children:[(0,W.jsx)(Y,{label:`Documents`,value:e?Dt(e.DocumentCount):`-`}),(0,W.jsx)(Y,{label:`Photos`,value:e?Dt(e.PhotoCount):`-`}),(0,W.jsx)(Y,{label:`Accounts with media`,value:e?Dt(e.AccountCount):`-`}),(0,W.jsx)(Y,{label:`Unattributed`,value:e?Pt(e.UnattributedBytes):`-`,tone:e&&Number(e.UnattributedBytes)>0?`warn`:`neutral`})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),b(!1)},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:f,onChange:e=>p(e.target.value),placeholder:`User ID / username / name`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:c,children:[c?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:x,disabled:c,children:[(0,W.jsx)(B,{size:15,className:c?`spin`:``}),` `,`Refresh`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(li,{label:`User ID`,sortKey:`user_id`,activeKey:m,desc:_,onSort:S}),(0,W.jsx)(li,{label:`Account`,sortKey:`username`,activeKey:m,desc:_,onSort:S}),(0,W.jsx)(li,{label:`Storage used`,sortKey:`bytes`,activeKey:m,desc:_,onSort:S}),(0,W.jsx)(li,{label:`Files`,sortKey:`files`,activeKey:m,desc:_,onSort:S})]})}),(0,W.jsxs)(`tbody`,{children:[n.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:e.UserID}),(0,W.jsx)(`td`,{children:H(e.Username)||e.FirstName||`-`}),(0,W.jsx)(`td`,{className:`mono`,children:Pt(e.Bytes)}),(0,W.jsx)(`td`,{className:`mono`,children:Dt(e.FileCount)})]},e.UserID)),n.length===0&&(0,W.jsx)(Bt,{colSpan:4})]})]})}),i&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>b(!0),disabled:c,children:[c?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})})]})}function di({navigate:e}){let[t,n]=(0,g.useState)(`overview`);return(0,W.jsxs)(G,{title:`Storage`,eyebrow:`Media / Storage usage`,children:[(0,W.jsxs)(`div`,{className:`tab-bar`,role:`tablist`,"aria-label":`Storage sections`,children:[(0,W.jsx)(`button`,{className:`tab-btn ${t===`overview`?`active`:``}`,type:`button`,role:`tab`,"aria-selected":t===`overview`,onClick:()=>n(`overview`),children:`Overview`}),(0,W.jsx)(`button`,{className:`tab-btn ${t===`limits`?`active`:``}`,type:`button`,role:`tab`,"aria-selected":t===`limits`,onClick:()=>n(`limits`),children:`Limits & Retention`})]}),t===`overview`?(0,W.jsx)(ui,{}):(0,W.jsx)(Ci,{})]})}var fi={blobBackend:`TELESRV_BLOB_BACKEND`,maxTotal:`TELESRV_STORAGE_MAX_TOTAL_BYTES`,minFree:`TELESRV_STORAGE_MIN_FREE_BYTES`,maxUploadFile:`TELESRV_STORAGE_MAX_UPLOAD_FILE_BYTES`,retentionMode:`TELESRV_STORAGE_RETENTION_MODE`,retentionMaxAge:`TELESRV_STORAGE_RETENTION_MAX_AGE`,evictionEnable:`TELESRV_STORAGE_EVICTION_ENABLE`},pi=[{key:`photo`,envKey:`TELESRV_STORAGE_RETENTION_MAX_AGE_PHOTO`,label:`Photo`},{key:`video`,envKey:`TELESRV_STORAGE_RETENTION_MAX_AGE_VIDEO`,label:`Video`},{key:`round_video`,envKey:`TELESRV_STORAGE_RETENTION_MAX_AGE_ROUND_VIDEO`,label:`Round video (video message)`},{key:`gif`,envKey:`TELESRV_STORAGE_RETENTION_MAX_AGE_GIF`,label:`GIF`},{key:`music`,envKey:`TELESRV_STORAGE_RETENTION_MAX_AGE_MUSIC`,label:`Music`},{key:`voice`,envKey:`TELESRV_STORAGE_RETENTION_MAX_AGE_VOICE`,label:`Voice message`},{key:`file`,envKey:`TELESRV_STORAGE_RETENTION_MAX_AGE_FILE`,label:`File`},{key:`avatar`,envKey:`TELESRV_STORAGE_RETENTION_MAX_AGE_AVATAR`,label:`Avatar`}],mi=524288*8e3,hi=[{label:`MB`,bytes:1024**2},{label:`GB`,bytes:1024**3},{label:`TB`,bytes:1024**4}];function gi(e){for(let t=hi.length-1;t>=0;t--)if(e>=hi[t].bytes)return hi[t];return hi[1]}function _i(e){let t=0,n=/(\d+(?:\.\d+)?)\s*(h|m|s)/g,r;for(;(r=n.exec(e))!==null;){let e=parseFloat(r[1]),n=r[2];t+=n===`h`?e*3600:n===`m`?e*60:e}return t/60}var vi=[{label:`Minutes`,minutes:1},{label:`Hours`,minutes:60},{label:`Days`,minutes:1440}];function yi(e){for(let t=vi.length-1;t>=0;t--)if(e>=vi[t].minutes)return vi[t];return vi[0]}function bi({label:e,help:t,minutes:n,disabled:r,onChange:i}){let a=Number(n||`0`),[o,s]=(0,g.useState)(()=>yi(a).label),c=vi.find(e=>e.label===o)??vi[2],l=Number.isFinite(a)?a/c.minutes:NaN;function u(e){if(!e.trim()){i(`0`);return}let t=Number(e);if(Number.isNaN(t)||t<0){i(`0`);return}if(t===0){i(`0`);return}i(String(Math.max(1,Math.round(t*c.minutes))))}return(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:e}),(0,W.jsxs)(`div`,{style:{display:`flex`,gap:8},children:[(0,W.jsx)(`input`,{type:`number`,min:`0`,step:`any`,value:Number.isNaN(l)?``:l,placeholder:`0`,disabled:r,onChange:e=>u(e.target.value)}),(0,W.jsx)(`select`,{value:o,disabled:r,onChange:e=>s(e.target.value),style:{maxWidth:100},children:vi.map(e=>(0,W.jsx)(`option`,{value:e.label,children:e.label},e.label))})]}),(0,W.jsx)(`span`,{className:`env-field-desc`,children:t})]})}function xi({label:e,help:t,bytes:n,onChange:r}){let i=Number(n||`0`),[a,o]=(0,g.useState)(()=>gi(i).label),s=hi.find(e=>e.label===a)??hi[1],c=i>0?i/s.bytes:NaN;function l(e){let t=Number(e);if(!e.trim()||Number.isNaN(t)||t<=0){r(`0`);return}r(String(Math.round(t*s.bytes)))}return(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:e}),(0,W.jsxs)(`div`,{style:{display:`flex`,gap:8},children:[(0,W.jsx)(`input`,{type:`number`,min:`0`,step:`any`,value:Number.isNaN(c)?``:c,placeholder:`Unlimited`,onChange:e=>l(e.target.value)}),(0,W.jsx)(`select`,{value:a,onChange:e=>o(e.target.value),style:{maxWidth:90},children:hi.map(e=>(0,W.jsx)(`option`,{value:e.label,children:e.label},e.label))})]}),(0,W.jsx)(`span`,{className:`env-field-desc`,children:t})]})}function Si({categoryAgeMinutes:e,onChange:t,disabled:n,onClose:r}){return(0,fn.createPortal)((0,W.jsx)(`div`,{className:`modal-backdrop`,role:`presentation`,children:(0,W.jsxs)(`section`,{className:`modal command-modal`,role:`dialog`,"aria-modal":`true`,"aria-label":`Per-category retention overrides`,children:[(0,W.jsxs)(`div`,{className:`modal-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`eyebrow`,children:`Limits & Retention`}),(0,W.jsx)(`h2`,{children:`Per-category overrides`})]}),(0,W.jsx)(`button`,{className:`icon-btn`,type:`button`,onClick:r,"aria-label":`Close`,children:(0,W.jsx)(yt,{size:15})})]}),(0,W.jsxs)(`div`,{className:`command-body`,children:[(0,W.jsx)(`p`,{className:`env-field-desc`,children:`Leave a category at 0 to inherit the shared Retention age. The mode switch still applies to all of them -- these only change how old that one category's media must be.`}),pi.map(r=>(0,W.jsx)(bi,{label:r.label,help:`Inherits the shared Retention age when left at 0.`,minutes:e[r.key]||`0`,disabled:n,onChange:e=>t(r.key,e)},r.key))]}),(0,W.jsx)(`div`,{className:`modal-actions`,children:(0,W.jsx)(`button`,{className:`btn primary`,type:`button`,onClick:r,children:`Close`})})]})}),document.body)}function Ci(){let[e,t]=(0,g.useState)(!1),[n,r]=(0,g.useState)(``),[i,a]=(0,g.useState)({}),[o,s]=(0,g.useState)(`s3`),[c,l]=(0,g.useState)(`0`),[u,d]=(0,g.useState)(`0`),[f,p]=(0,g.useState)(`0`),[m,h]=(0,g.useState)(`off`),[_,v]=(0,g.useState)(`43200`),[y,b]=(0,g.useState)({}),[x,S]=(0,g.useState)(!1),[C,w]=(0,g.useState)(!1);async function T(){r(``);try{let e=await k.serverEnv(),n={};for(let t of e)for(let e of t.fields)n[e.key]=e.value||e.default_value||``;a(n),s((n[fi.blobBackend]||`s3`).trim().toLowerCase()),l(n[fi.maxTotal]||`0`),d(n[fi.minFree]||`0`),p(n[fi.maxUploadFile]||`0`);let r=(n[fi.retentionMode]||`off`).trim().toLowerCase();h(r===`orphan`||r===`hard`?r:`off`);let i=_i(n[fi.retentionMaxAge]||`720h`);v(i>0?String(Math.max(1,Math.round(i))):`43200`);let o={};for(let e of pi){let t=_i(n[e.envKey]||``);o[e.key]=t>0?String(Math.round(t)):`0`}b(o),w((n[fi.evictionEnable]||`false`).trim().toLowerCase()===`true`),t(!0)}catch(e){r(O(e))}}(0,g.useEffect)(()=>{T()},[]);let E=(0,g.useMemo)(()=>{let e={[fi.maxTotal]:c||`0`,[fi.minFree]:u||`0`,[fi.maxUploadFile]:f||`0`,[fi.retentionMode]:m,[fi.retentionMaxAge]:m===`off`?i[fi.retentionMaxAge]||`720h`:`${Math.max(0,Math.round(Number(_||`0`)))}m`,[fi.evictionEnable]:C?`true`:`false`};for(let t of pi){let n=Math.round(Number(y[t.key]||`0`));e[t.envKey]=n>0?`${n}m`:``}let t={};for(let[n,r]of Object.entries(e))(i[n]??``)!==r&&(t[n]=r);return t},[c,u,f,m,_,y,C,i]),D=Object.keys(E).length>0,A=Number(f||`0`)>mi;return(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Limits & Retention`,text:`Server-wide storage budget, per-file upload cap, and automatic media cleanup. Saved to .env -- takes effect on the next Restart/Update.`}),n&&(0,W.jsx)(q,{children:n}),e?(0,W.jsxs)(`div`,{className:`card-body`,children:[(0,W.jsxs)(`div`,{className:`attr-block`,children:[(0,W.jsx)(xi,{label:`Max total storage budget`,help:`Reject new uploads once total tracked blob bytes would exceed this. Empty/0 = unlimited.`,bytes:c,onChange:l}),o===`localfs`&&(0,W.jsx)(xi,{label:`Min free space guard`,help:`localfs backend only: reject new uploads once real free disk space falls below this. Empty/0 disables the check.`,bytes:u,onChange:d}),(0,W.jsx)(xi,{label:`Max single file size`,help:`Reject a single upload once its total assembled size exceeds this. Empty/0 = unlimited, bounded only by the protocol's own ~4GB per-file ceiling.`,bytes:f,onChange:p}),A&&(0,W.jsx)(q,{children:`This exceeds the protocol's own ~4GB upload ceiling and will be refused when the server restarts.`})]}),(0,W.jsxs)(`div`,{className:`attr-block`,style:{marginTop:`1.5em`},children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Retention mode`}),(0,W.jsxs)(`select`,{value:m,onChange:e=>h(e.target.value),children:[(0,W.jsx)(`option`,{value:`off`,children:`Off`}),(0,W.jsx)(`option`,{value:`orphan`,children:`Delete once no longer used (safe)`}),(0,W.jsx)(`option`,{value:`hard`,children:`Delete after a fixed time, even if still in use`})]})]}),(0,W.jsxs)(`p`,{className:`env-field-desc`,children:[m===`off`&&`No storage sweep runs; nothing is auto-deleted. Storage usage is still tracked and shown above either way.`,m===`orphan`&&`Safe: a document or photo's file is deleted only once it is no longer referenced by any message, profile photo, or sticker set. Media still visible in a conversation is never touched, regardless of age.`,m===`hard`&&`Irreversible and aggressive: a document or photo's file bytes are deleted once old enough, REGARDLESS of whether a message still references it. Old media in active conversations will start showing as unavailable once purged -- only the file is removed, the message itself keeps rendering its placeholder (name, size, thumbnail).`]}),(0,W.jsx)(bi,{label:`Retention age`,help:(m===`hard`?`How old the media itself must be, counted from when it was uploaded, before its bytes are purged.`:`How long a document or photo must have had zero references before its file is deleted.`)+` Set to 0 to disable the sweep by default and only clean up categories you explicitly override below.`,minutes:_,disabled:m===`off`,onChange:v})]}),(0,W.jsxs)(`div`,{className:`attr-block`,style:{marginTop:`1.5em`},children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>S(!0),children:[(0,W.jsx)(nt,{size:15}),` `,`Per-category overrides...`]}),(0,W.jsx)(`span`,{className:`env-field-desc`,children:`Optional -- give Photo/Video/GIF/Music/Voice/File/Avatar their own retention age instead of the shared one above.`})]}),x&&(0,W.jsx)(Si,{categoryAgeMinutes:y,onChange:(e,t)=>b(n=>({...n,[e]:t})),disabled:m===`off`,onClose:()=>S(!1)}),(0,W.jsxs)(`div`,{className:`attr-block`,style:{marginTop:`1.5em`},children:[(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:C,onChange:e=>w(e.target.checked)}),` `,`Actively reclaim space once over budget`]}),(0,W.jsx)(`p`,{className:`env-field-desc`,children:`Once total physical storage exceeds the Max total storage budget above, actively delete the oldest files (regardless of category or age) until back under budget -- the same way "hard" retention mode purges files. Independent of the retention mode above: this can run even when that's Off. Off by default, since this changes the storage budget from block-new-uploads-only to also reclaiming from existing files.`})]}),(0,W.jsx)(`div`,{className:`gift-table-actions env-save-row`,children:(0,W.jsx)(Z,{tone:`warn`,label:`Save limits & retention settings`,path:`/api/actions/update-server-env`,payload:()=>({values:E}),disabled:!D||A,onDone:()=>void T()})})]}):(0,W.jsx)(`p`,{style:{color:`var(--muted)`},children:`Loading current settings...`})]})}function wi({loader:e,cacheKey:t,className:n,playOnHover:r=!0,onError:i}){let a=(0,g.useRef)(null),o=(0,g.useRef)(null);(0,g.useEffect)(()=>{let t=!1;return e().then(e=>{t||!a.current||(o.current?.destroy(),o.current=Sr.default.loadAnimation({container:a.current,renderer:`canvas`,loop:!0,autoplay:!1,animationData:structuredClone(e)}),o.current.goToAndStop(0,!0))}).catch(()=>i?.()),()=>{t=!0,o.current?.destroy(),o.current=null}},[t]);function s(){r&&o.current?.play()}function c(){r&&o.current?.goToAndStop(0,!0)}return(0,W.jsx)(`div`,{className:n,ref:a,onMouseEnter:s,onMouseLeave:c})}function Ti(e){let t=e.toLowerCase();return t.includes(`tgsticker`)||t.includes(`lottie`)||t.includes(`json`)}function Ei({row:e}){let[t,n]=(0,g.useState)(!Ti(e.MimeType));return(0,g.useEffect)(()=>{n(!Ti(e.MimeType))},[e.DocumentID,e.MimeType]),t?(0,W.jsx)(`div`,{className:`emoji-picker-glyph`,children:e.Alt||`🙂`}):(0,W.jsx)(wi,{className:`emoji-picker-anim`,cacheKey:e.DocumentID,loader:()=>k.emojiAnimation(e.DocumentID),onError:()=>n(!0)})}function Di({label:e,value:t,onChange:n}){let[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)([]),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(``),d=a.find(e=>e.DocumentID===t)??null;async function f(){c(!0),u(``);let e=new URLSearchParams({limit:`24`});r.trim()&&e.set(`q`,r.trim());try{o((await k.emoji(e)).rows??[])}catch(e){u(O(e))}finally{c(!1)}}return(0,g.useEffect)(()=>{f()},[]),(0,W.jsxs)(`div`,{className:`entity-picker`,children:[(0,W.jsxs)(`div`,{className:`picker-head`,children:[(0,W.jsx)(`span`,{children:e}),t?(0,W.jsxs)(`button`,{className:`link-button`,type:`button`,onClick:()=>n(``),children:[(0,W.jsx)(yt,{size:13}),` `,`Clear`]}):null]}),t?(0,W.jsxs)(`div`,{className:`selected-entity`,children:[(0,W.jsx)(ye,{size:15}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`strong`,{children:d?.Alt||`—`}),(0,W.jsx)(`span`,{className:`mono`,children:t})]}),(0,W.jsx)(`span`,{children:d?.SetTitle||`-`})]}):null,(0,W.jsxs)(`div`,{className:`picker-search`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),f())},placeholder:`Search document ID or emoji`}),(0,W.jsx)(`button`,{className:`btn compact-btn`,type:`button`,onClick:f,disabled:s,children:s?(0,W.jsx)(I,{size:14,className:`spin`}):`Search`})]}),l&&(0,W.jsx)(`div`,{className:`picker-error`,children:l}),(0,W.jsxs)(`div`,{className:`picker-results emoji-picker-results`,children:[a.map(e=>(0,W.jsxs)(`button`,{className:`picker-row emoji-picker-row ${t===e.DocumentID?`selected`:``}`,type:`button`,onClick:()=>n(e.DocumentID),children:[(0,W.jsx)(Ei,{row:e}),(0,W.jsx)(`span`,{className:`mono`,children:e.DocumentID}),(0,W.jsx)(`span`,{children:e.SetTitle||`—`})]},e.DocumentID)),a.length===0&&!s?(0,W.jsx)(`div`,{className:`picker-empty`,children:`No results`}):null]})]})}var Oi=[`pending`,`approved`,`rejected`,`revoked`],ki=[`user`,`channel`],Ai={pending:`Pending`,approved:`Approved`,rejected:`Rejected`,revoked:`Mark revoked`},ji={user:`Account`,channel:`Channel`};function Mi({navigate:e}){let{can:t}=Xt(),n=t(Kt),r=t(Wt),[i,a]=(0,g.useState)(`requests`),[o,s]=(0,g.useState)([]),[c,l]=(0,g.useState)([]),[u,d]=(0,g.useState)(``),[f,p]=(0,g.useState)(!1);async function m(){d(``),p(!1);try{let[e,t]=await Promise.all([k.botVerifiers(new URLSearchParams({limit:`200`})),k.verificationIcons(new URLSearchParams({limit:`200`}))]);s(e.rows??[]),l(t.rows??[])}catch(e){if(e instanceof v&&e.status===403){s([]),l([]),p(!0);return}d(O(e))}}return(0,g.useEffect)(()=>{m()},[]),(0,W.jsxs)(G,{title:`Third-party verification`,eyebrow:`Third-party verification / Verifiers, icons, marks`,actions:r?(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>e(`/verification`),children:[(0,W.jsx)(De,{size:15}),` `,`Official verification`]}):void 0,children:[u&&(0,W.jsx)(q,{children:u}),f&&(0,W.jsx)(q,{children:`The server refused the verifier roster and the icon catalogue for this session (403), so both lists are empty here — applications can still be reviewed.`}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`A verifier company's icon — not the official checkmark`,text:`A third-party mark is a verifier bot's own icon, drawn right BEFORE the name of an account, a bot or a channel, plus one line of description in the profile. It says “this verifier vouches for this peer”, and nothing more.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The icon is a custom emoji document. The client fetches it through messages.getCustomEmojiDocuments, so a document id that resolves to nothing renders as no badge at all — which is why marks are granted from the catalogue below rather than from a typed number.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The official checkmark is a different mechanism, granted by the platform in the Verification section. The two are stored, shown and taken away separately, and neither one implies the other.`}),!n&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This session can read the section and decide applications, but not change verifiers or the icon catalogue — that needs the botverification.manage permission.`})]}),(0,W.jsx)(`div`,{className:`toolbar`,role:`group`,"aria-label":`Third-party verification`,children:[{key:`requests`,label:`Applications`,icon:(0,W.jsx)(ut,{size:15})},{key:`verifiers`,label:`Verifiers`,icon:(0,W.jsx)(_e,{size:15})},{key:`icons`,label:`Icon catalogue`,icon:(0,W.jsx)(dt,{size:15})},{key:`marks`,label:`Granted marks`,icon:(0,W.jsx)(ee,{size:15})}].map(e=>(0,W.jsxs)(`button`,{className:`btn icon-text ${i===e.key?`primary`:``}`,type:`button`,"aria-pressed":i===e.key,onClick:()=>a(e.key),children:[e.icon,` `,e.label]},e.key))}),i===`requests`&&(0,W.jsx)(Ni,{navigate:e,verifiers:o}),i===`verifiers`&&(0,W.jsx)(Pi,{verifiers:o,icons:c,canManage:n,onChanged:m,navigate:e}),i===`icons`&&(0,W.jsx)(Fi,{icons:c,verifiers:o,canManage:n,onChanged:m}),i===`marks`&&(0,W.jsx)(Ii,{verifiers:o,canManage:n,navigate:e})]})}function Ni({navigate:e,verifiers:t}){let[n,r]=(0,g.useState)(`pending`),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(`all`),[c,l]=(0,g.useState)(``),[u,d]=(0,g.useState)(`50`),[f,p]=(0,g.useState)([]),[m,h]=(0,g.useState)({}),[_,v]=(0,g.useState)(!1),[y,b]=(0,g.useState)(``),[x,S]=(0,g.useState)(!1),[C,w]=(0,g.useState)(``);async function T(e=!1){S(!0),w(``);let t=new URLSearchParams({limit:u});n!==`all`&&t.set(`status`,n),i&&t.set(`verifier_bot_id`,i),o!==`all`&&t.set(`peer_type`,o),c.trim()&&t.set(`q`,c.trim().replace(/^@/,``)),e&&y&&t.set(`before_id`,y);try{let n=await k.customVerificationRequests(t),r=n.rows??[];p(t=>e?[...t,...r]:r),b(n.next_before_id??``),v(!!n.has_more)}catch(e){w(O(e))}finally{S(!1)}}async function E(){try{h((await k.botVerificationCounts()).counts??{})}catch(e){w(O(e))}}(0,g.useEffect)(()=>{T(!1),E()},[]);function D(){T(!1),E()}return(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Application queue`,text:`Applications filed with a verifier bot by the owner of the peer. The counters cover the whole queue, not the page below.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:D,disabled:x,children:[(0,W.jsx)(B,{size:15,className:x?`spin`:``}),` `,`Refresh`]})}),C&&(0,W.jsx)(q,{children:C}),(0,W.jsx)(`div`,{className:`metric-row`,children:Oi.map(e=>(0,W.jsx)(Y,{label:Ai[e],value:m[e]??`0`,mono:!0,tone:Bi(e,m[e]??`0`)},e))})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),T(!1)},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:c,onChange:e=>l(e.target.value),placeholder:`Application id, peer id, username or title`})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Status`}),(0,W.jsxs)(`select`,{value:n,onChange:e=>r(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All statuses`}),Oi.map(e=>(0,W.jsx)(`option`,{value:e,children:Ai[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Verifier`}),(0,W.jsx)(Li,{value:i,verifiers:t,onChange:a})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Peer type`}),(0,W.jsxs)(`select`,{value:o,onChange:e=>s(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All types`}),ki.map(e=>(0,W.jsx)(`option`,{value:e,children:ji[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsx)(`input`,{className:`small-input`,value:u,onChange:e=>d(e.target.value),type:`number`,min:`1`,max:`200`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:x,children:[x?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Verifier`}),(0,W.jsx)(`th`,{children:`Peer`}),(0,W.jsx)(`th`,{children:`Applicant`}),(0,W.jsx)(`th`,{children:`Stated reason`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Filed`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[f.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:(0,W.jsxs)(`button`,{className:`row-link`,type:`button`,onClick:()=>e(`/bot-verification/${t.ID}`),children:[`#`,t.ID]})}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:H(t.VerifierBotUsername)||t.VerifierBotID}),(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:t.VerifierBotID})]}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:Vi(t)}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[ji[t.PeerType],` · `,t.PeerID]})]}),(0,W.jsxs)(`td`,{children:[H(t.ApplicantUsername)||`-`,(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:t.ApplicantUserID})]}),(0,W.jsx)(`td`,{className:`truncate`,children:t.Reason||`-`}),(0,W.jsx)(`td`,{children:(0,W.jsx)(Ri,{status:t.Status})}),(0,W.jsx)(`td`,{children:U(t.CreatedAt)||`-`}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,type:`button`,onClick:()=>e(`/bot-verification/${t.ID}`),children:[(0,W.jsx)(ut,{size:14}),` `,`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),f.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})}),_&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>T(!0),disabled:x,children:[x?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})})]})}function Pi({verifiers:e,icons:t,canManage:n,onChanged:r,navigate:i}){let[a,o]=(0,g.useState)(null),[s,c]=(0,g.useState)(null),[l,u]=(0,g.useState)(``),[d,f]=(0,g.useState)(``),[p,m]=(0,g.useState)(``),[h,_]=(0,g.useState)(!1),v=t.filter(e=>e.Active),y=v.map(e=>({value:e.DocumentID,label:`${e.Name} · ${e.DocumentID}`}));if(l&&!y.some(e=>e.value===l)){let e=t.find(e=>e.DocumentID===l);y.unshift({value:l,label:`${e?.Name??l} · ${l} (Retired)`})}function b(e){c(e),o(null),u(e.IconDocumentID),f(e.CompanyName),m(e.DefaultDescription),_(e.CanModifyCustomDescription)}function x(){c(null),o(null),u(``),f(``),m(``),_(!1)}function S(){return{bot_id:s?s.BotID:a?String(a.ID):`0`,icon_document_id:l||`0`,company_name:d.trim(),default_description:p.trim(),can_modify_custom_description:h,version:s?s.Version:`0`}}return(0,W.jsxs)(W.Fragment,{children:[n&&(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:s?`Update verifier`:`Grant verifier status`,text:`The bot gets an icon from the catalogue and a company name to vouch under. The same call updates an existing verifier, which is why it carries a version.`,action:s?(0,W.jsx)(`button`,{className:`btn icon-text`,type:`button`,onClick:x,children:`Cancel update`}):void 0}),s?(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Updating ${H(s.BotUsername)||s.BotID} — version ${s.Version} is sent as the optimistic lock, so a row somebody else changed meanwhile is refused instead of overwritten.`}):(0,W.jsx)(qn,{label:`Bot`,value:a,onChange:o}),(0,W.jsxs)(`div`,{className:`bot-create-fields`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Icon from the catalogue`}),(0,W.jsxs)(`select`,{value:l,onChange:e=>u(e.target.value),children:[(0,W.jsx)(`option`,{value:``,children:`Pick an icon`}),y.map(e=>(0,W.jsx)(`option`,{value:e.value,children:e.label},e.value))]})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Company`}),(0,W.jsx)(`input`,{value:d,onChange:e=>f(e.target.value),placeholder:`Acme Verification Ltd`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Default description`}),(0,W.jsx)(`input`,{value:p,onChange:e=>m(e.target.value),placeholder:`Verified by Acme`})]})]}),(0,W.jsxs)(`label`,{className:`checkline`,children:[(0,W.jsx)(`input`,{type:`checkbox`,checked:h,onChange:e=>_(e.target.checked)}),`The verifier may replace the description per peer`]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This is botVerifierSettings.can_modify_custom_description: with it off, every mark this verifier grants carries the default description above, whatever the applicant asked for.`}),v.length===0&&(0,W.jsx)(q,{children:`The catalogue has no active icon, so there is nothing to grant. Add one in the icon catalogue first.`}),(0,W.jsxs)(`div`,{className:`bot-create-actions`,children:[(0,W.jsx)(`span`,{className:`bot-create-note`,children:`The bot can mark peers as soon as the row exists and is enabled.`}),(0,W.jsx)(Z,{label:s?`Update verifier`:`Grant verifier status`,icon:(0,W.jsx)(Ye,{size:15}),tone:`neutral`,path:`/api/actions/grant-bot-verifier`,payload:S,onDone:()=>{x(),r()}})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Verifier bots`,text:`Bots allowed to hand out their own mark. Verifier status is granted per deployment, so every row here is a badge printer an operator switched on by hand.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:r,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Bot`}),(0,W.jsx)(`th`,{children:`Company`}),(0,W.jsx)(`th`,{children:`Icon`}),(0,W.jsx)(`th`,{children:`Own description`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Marks`}),(0,W.jsx)(`th`,{children:`Granted by`}),(0,W.jsx)(`th`,{children:`Updated`}),n&&(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[e.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`button`,{className:`row-link`,type:`button`,onClick:()=>i(`/bots/${e.BotID}`),children:(0,W.jsx)(`strong`,{children:H(e.BotUsername)||e.BotName||e.BotID})}),(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:e.BotID})]}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:e.CompanyName||`-`}),(0,W.jsx)(`div`,{className:`entity-subtitle truncate`,children:e.DefaultDescription||`Not set`})]}),(0,W.jsxs)(`td`,{children:[e.IconName||`-`,(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:e.IconDocumentID})]}),(0,W.jsx)(`td`,{children:e.CanModifyCustomDescription?`Yes`:`No`}),(0,W.jsx)(`td`,{children:e.Enabled?(0,W.jsx)(J,{tone:`good`,children:`Enabled`}):(0,W.jsx)(J,{tone:`warn`,children:`disabled`})}),(0,W.jsx)(`td`,{className:`mono`,children:String(e.MarkCount??`0`)}),(0,W.jsxs)(`td`,{children:[e.GrantedBy||`-`,(0,W.jsx)(`div`,{className:`entity-subtitle truncate`,children:e.GrantReason||`-`})]}),(0,W.jsx)(`td`,{children:U(e.UpdatedAt)||`-`}),n&&(0,W.jsx)(`td`,{children:(0,W.jsxs)(`div`,{className:`row-actions`,children:[(0,W.jsx)(`button`,{className:`btn compact-btn`,type:`button`,onClick:()=>b(e),children:`Edit`}),(0,W.jsx)(Z,{label:e.Enabled?`Disable`:`Enable`,icon:e.Enabled?(0,W.jsx)(Xe,{size:14}):(0,W.jsx)(Ze,{size:14}),tone:e.Enabled?`warn`:`neutral`,compact:!0,path:`/api/actions/set-bot-verifier-enabled`,payload:()=>({bot_id:e.BotID,enabled:!e.Enabled}),onDone:r}),(0,W.jsx)(Z,{label:`Revoke status`,icon:(0,W.jsx)(pt,{size:14}),tone:`danger`,compact:!0,path:`/api/actions/revoke-bot-verifier`,payload:()=>({bot_id:e.BotID}),onDone:r})]})})]},e.BotID)),e.length===0&&(0,W.jsx)(Bt,{colSpan:n?9:8})]})]})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Disabling is the per-verifier kill switch: the marks already granted keep rendering, but the bot can no longer mark anything new and its settings stop being projected into botInfo.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Revoking verifier status removes the row and every mark this verifier granted — the icon disappears from all of its peers at once.`})]})]})}function Fi({icons:e,verifiers:t,canManage:n,onChanged:r}){let[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(``),[c,l]=(0,g.useState)(``);function u(){let e={document_id:i.trim()||`0`,name:o.trim()};return c&&(e.owner_bot_id=c),e}return(0,W.jsxs)(W.Fragment,{children:[n&&(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Add or rename an icon`,text:`Search and pick any custom-emoji document already on this deployment (including bundled/system ones). Adding an id that already exists renames it instead of duplicating it.`}),(0,W.jsx)(Di,{label:`Document`,value:i,onChange:a}),(0,W.jsxs)(`div`,{className:`bot-create-fields`,children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Name`}),(0,W.jsx)(`input`,{value:o,onChange:e=>s(e.target.value),placeholder:`Acme blue tick`})]}),(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Owner`}),(0,W.jsxs)(`select`,{value:c,onChange:e=>l(e.target.value),children:[(0,W.jsx)(`option`,{value:``,children:`Shared`}),t.map(e=>(0,W.jsx)(`option`,{value:e.BotID,children:`${e.CompanyName||e.BotID} · ${H(e.BotUsername)||e.BotID}`},e.BotID))]})]})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`A document id that resolves to nothing produces an invisible badge: the peer is marked in the database and the client draws nothing.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`A shared icon may be granted to any verifier; picking an owner reserves it for that one bot.`}),(0,W.jsxs)(`div`,{className:`bot-create-actions`,children:[(0,W.jsx)(`span`,{className:`bot-create-note`,children:`Adding an icon grants nothing by itself — it only makes the document available to grant.`}),(0,W.jsx)(Z,{label:`Save icon`,icon:(0,W.jsx)(Ye,{size:15}),tone:`neutral`,path:`/api/actions/upsert-verification-icon`,payload:u,onDone:()=>{a(``),s(``),l(``),r()}})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Icon catalogue`,text:`The custom emoji documents a verifier may mark with. Nothing else can be used as an icon, so the catalogue is where a wrong badge is prevented rather than fixed.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:r,children:[(0,W.jsx)(B,{size:15}),` `,`Refresh`]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Document ID`}),(0,W.jsx)(`th`,{children:`Name`}),(0,W.jsx)(`th`,{children:`Owner`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Verifiers using it`}),(0,W.jsx)(`th`,{children:`Filed`}),n&&(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[e.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:e.DocumentID}),(0,W.jsx)(`td`,{children:(0,W.jsx)(`strong`,{children:e.Name||`-`})}),(0,W.jsx)(`td`,{children:e.OwnerBotID&&e.OwnerBotID!==`0`?(0,W.jsxs)(W.Fragment,{children:[H(e.OwnerBotUsername)||e.OwnerBotID,(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:e.OwnerBotID})]}):(0,W.jsx)(J,{children:`Shared`})}),(0,W.jsx)(`td`,{children:e.Active?(0,W.jsx)(J,{tone:`good`,children:`Active`}):(0,W.jsx)(J,{tone:`warn`,children:`Retired`})}),(0,W.jsx)(`td`,{className:`mono`,children:String(e.UsedByVerifiers??`0`)}),(0,W.jsx)(`td`,{children:U(e.CreatedAt)||`-`}),n&&(0,W.jsx)(`td`,{children:(0,W.jsx)(`div`,{className:`row-actions`,children:(0,W.jsx)(Z,{label:e.Active?`Retire`:`Activate`,icon:e.Active?(0,W.jsx)(Xe,{size:14}):(0,W.jsx)(Ze,{size:14}),tone:e.Active?`warn`:`neutral`,compact:!0,path:`/api/actions/set-verification-icon-active`,payload:()=>({icon_id:e.ID,active:!e.Active}),onDone:r})})})]},e.ID)),e.length===0&&(0,W.jsx)(Bt,{colSpan:n?7:6})]})]})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Retiring an icon stops it from being granted to anybody new. Marks already carrying it keep it: the icon is copied onto the mark when it is granted.`})]})]})}function Ii({verifiers:e,canManage:t,navigate:n}){let[r,i]=(0,g.useState)(``),[a,o]=(0,g.useState)(`all`),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(`50`),[d,f]=(0,g.useState)([]),[p,m]=(0,g.useState)(!1),[h,_]=(0,g.useState)(``),[v,y]=(0,g.useState)(!1),[b,x]=(0,g.useState)(``);async function S(e=!1){y(!0),x(``);let t=new URLSearchParams({limit:l});r&&t.set(`verifier_bot_id`,r),a!==`all`&&t.set(`peer_type`,a),s.trim()&&t.set(`q`,s.trim().replace(/^@/,``)),e&&h&&t.set(`before_id`,h);try{let n=await k.customVerifications(t),r=n.rows??[];f(t=>e?[...t,...r]:r),_(n.next_before_id??``),m(!!n.has_more)}catch(e){x(O(e))}finally{y(!1)}}return(0,g.useEffect)(()=>{S(!1)},[]),(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Granted marks`,text:`Every peer currently carrying a third-party mark, whoever granted it — an operator decision, the verifier bot itself, or the peer's owner through bots.setCustomVerification.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>S(!1),disabled:v,children:[(0,W.jsx)(B,{size:15,className:v?`spin`:``}),` `,`Refresh`]})}),b&&(0,W.jsx)(q,{children:b})]}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),S(!1)},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:s,onChange:e=>c(e.target.value),placeholder:`Peer id, username or title`})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Verifier`}),(0,W.jsx)(Li,{value:r,verifiers:e,onChange:i})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Peer type`}),(0,W.jsxs)(`select`,{value:a,onChange:e=>o(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All types`}),ki.map(e=>(0,W.jsx)(`option`,{value:e,children:ji[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsx)(`input`,{className:`small-input`,value:l,onChange:e=>u(e.target.value),type:`number`,min:`1`,max:`200`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:v,children:[v?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Verifier`}),(0,W.jsx)(`th`,{children:`Peer`}),(0,W.jsx)(`th`,{children:`Description`}),(0,W.jsx)(`th`,{children:`Icon`}),(0,W.jsx)(`th`,{children:`Filed`}),t&&(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[d.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsxs)(`td`,{className:`mono`,children:[`#`,e.ID]}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:e.CompanyName||H(e.VerifierBotUsername)||e.VerifierBotID}),(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:H(e.VerifierBotUsername)||e.VerifierBotID})]}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`button`,{className:`row-link`,type:`button`,onClick:()=>n(Hi(e.PeerType,e.PeerID)),children:(0,W.jsx)(`strong`,{children:Vi(e)})}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[ji[e.PeerType],` · `,e.PeerID]})]}),(0,W.jsx)(`td`,{className:`truncate`,children:e.Description||`Not set`}),(0,W.jsx)(`td`,{className:`mono`,children:e.IconDocumentID}),(0,W.jsx)(`td`,{children:U(e.CreatedAt)||`-`}),t&&(0,W.jsx)(`td`,{children:(0,W.jsx)(`div`,{className:`row-actions`,children:(0,W.jsx)(Z,{label:`Remove mark`,icon:(0,W.jsx)(he,{size:14}),tone:`danger`,compact:!0,path:`/api/actions/revoke-custom-verification`,payload:()=>({verifier_bot_id:e.VerifierBotID,peer_type:e.PeerType,peer_id:e.PeerID}),onDone:()=>S(!1)})})})]},e.ID)),d.length===0&&(0,W.jsx)(Bt,{colSpan:t?7:6})]})]})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Removing a mark clears the icon and the description from the peer. The application it came from keeps its history.`}),p&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>S(!0),disabled:v,children:[v?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})})]})}function Li({value:e,verifiers:t,onChange:n}){return(0,W.jsxs)(`select`,{value:e,onChange:e=>n(e.target.value),children:[(0,W.jsx)(`option`,{value:``,children:`All verifiers`}),t.map(e=>(0,W.jsx)(`option`,{value:e.BotID,children:`${e.CompanyName||e.BotID} · ${H(e.BotUsername)||e.BotID}`+(e.Enabled?``:` (disabled)`)},e.BotID))]})}function Ri({status:e}){return(0,W.jsx)(J,{tone:zi(e),children:Ai[e]})}function zi(e){return e===`approved`?`good`:e===`pending`?`warn`:e===`rejected`?`danger`:`neutral`}function Bi(e,t){return e===`pending`?t!==`0`&&t!==``?`warn`:`neutral`:e===`approved`?`good`:`neutral`}function Vi(e){return H(e.PeerUsername)||e.PeerTitle||`#${e.PeerID}`}function Hi(e,t){return e===`channel`?`/channels/${t}`:`/accounts/${t}`}function Ui({id:e,navigate:t}){let[n,r]=(0,g.useState)(null),[i,a]=(0,g.useState)(``),[o,s]=(0,g.useState)(!1),[c,l]=(0,g.useState)(!1),[u,d]=(0,g.useState)(``);async function f(){l(!0),d(``);try{r(await k.customVerificationRequest(e))}catch(e){d(O(e))}finally{l(!1)}}function p(){s(!1),f()}(0,g.useEffect)(()=>{f()},[e]);function m(e){if(e instanceof v&&e.status===409)return s(!0),f(),`Another admin has already changed this application. The data has been reloaded — check the status before deciding again.`}if(u&&!n)return(0,W.jsx)(q,{children:u});if(!n)return(0,W.jsx)(Vt,{label:`Loading the application…`});let h=n.request,_=Gi(n.verifier),y=n.mark_active,b=h.Status===`pending`,x=h.Status===`approved`,S=i.trim(),C=h.RequestedDescription.trim(),w=!!_?.CanModifyCustomDescription&&C!==``,T=w?C:(_?.DefaultDescription??``).trim();function E(){let e={version:h.Version};return S&&(e.internal_note=S),e}function D(){a(``),s(!1),f()}return(0,W.jsxs)(G,{title:`Application #${h.ID}`,eyebrow:`Third-party verification / Review`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/bot-verification`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to list`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:p,disabled:c,children:[(0,W.jsx)(B,{size:15,className:c?`spin`:``}),` `,`Refresh`]})]}),children:[u&&(0,W.jsx)(q,{children:u}),o&&(0,W.jsx)(q,{children:`Another admin has already changed this application. The data has been reloaded — check the status before deciding again.`}),(0,W.jsx)(Rt,{main:(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:Vi(h)}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[`#`,h.ID,` · `,ji[h.PeerType],`:`,h.PeerID,` · v`,h.Version]})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[(0,W.jsx)(Ri,{status:h.Status}),y?(0,W.jsxs)(J,{tone:`good`,children:[(0,W.jsx)(ee,{size:12}),` `,`Mark is live`]}):(0,W.jsx)(J,{tone:`neutral`,children:`No mark on the peer`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`A verifier company's icon — not the official checkmark`,text:`A third-party mark is a verifier bot's own icon, drawn right BEFORE the name of an account, a bot or a channel, plus one line of description in the profile. It says “this verifier vouches for this peer”, and nothing more.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The icon is a custom emoji document. The client fetches it through messages.getCustomEmojiDocuments, so a document id that resolves to nothing renders as no badge at all — which is why marks are granted from the catalogue below rather than from a typed number.`})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Verifier`,text:`The company whose icon the peer would carry, as its row stands right now.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/bots/${h.VerifierBotID}`),children:[(0,W.jsx)(_e,{size:15}),` `,`Open verifier bot`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Company`,value:_?.CompanyName||`-`}),(0,W.jsx)(X,{label:`Bot`,value:H(h.VerifierBotUsername)||`-`}),(0,W.jsx)(X,{label:`Verifier bot ID`,value:h.VerifierBotID,mono:!0}),(0,W.jsx)(X,{label:`Document ID`,value:_?.IconDocumentID||`-`,mono:!0}),(0,W.jsx)(X,{label:`Name`,value:_?.IconName||`-`}),(0,W.jsx)(X,{label:`Own description`,value:_?.CanModifyCustomDescription?`Yes`:`No`})]}),(0,W.jsx)(Wi,{label:`Default description`,children:_?.DefaultDescription?(0,W.jsx)(`p`,{className:`about-text`,children:_.DefaultDescription}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})}),!_&&(0,W.jsx)(q,{children:`The verifier row is gone: its status was revoked after this application was filed. There is no icon to grant, so the application can only be rejected.`}),_&&!_.Enabled&&(0,W.jsx)(q,{children:`This verifier is disabled. It cannot mark anything new until an operator enables it again.`})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Peer`,text:`The account, bot or channel the icon would be attached to.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(Hi(h.PeerType,h.PeerID)),children:[(0,W.jsx)(De,{size:15}),` `,`Open peer`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Type`,value:ji[h.PeerType]}),(0,W.jsx)(X,{label:`Username`,value:H(h.PeerUsername)||`-`}),(0,W.jsx)(X,{label:`Title`,value:h.PeerTitle||`-`}),(0,W.jsx)(X,{label:`Peer ID`,value:h.PeerID,mono:!0})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Applicant`,text:`Who filed the application with the verifier bot.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/accounts/${h.ApplicantUserID}`),children:[(0,W.jsx)(gt,{size:15}),` `,`Open account`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Username`,value:H(h.ApplicantUsername)||`-`}),(0,W.jsx)(X,{label:`User ID`,value:h.ApplicantUserID,mono:!0}),(0,W.jsx)(X,{label:`Filed`,value:U(h.CreatedAt)||`-`}),(0,W.jsx)(X,{label:`Updated`,value:U(h.UpdatedAt)||`-`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Application`,text:`What the applicant wrote, rendered as plain text.`}),(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Correlation ID`,value:h.CorrelationID||`-`,mono:!0}),(0,W.jsx)(X,{label:`Status`,value:Ai[h.Status]})]}),(0,W.jsx)(Wi,{label:`Stated reason`,children:h.Reason?(0,W.jsx)(`p`,{className:`about-text`,children:h.Reason}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})}),(0,W.jsx)(Wi,{label:`Requested description`,children:C?(0,W.jsx)(`p`,{className:`about-text`,children:C}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})}),(0,W.jsx)(Wi,{label:`Description the mark would carry`,children:T?(0,W.jsx)(`p`,{className:`about-text`,children:T}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Resolved the same way the backend resolves it: the applicant's wording only when this verifier may set its own description, otherwise the verifier's default.`}),C!==``&&!w&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This verifier may not set a per-peer description, so the requested wording is ignored and the default is applied.`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Decision`,text:`What was decided, by whom, and with which wording.`}),(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Decided by`,value:h.DecidedBy||`-`}),(0,W.jsx)(X,{label:`Approved`,value:U(h.ApprovedAt)||`-`}),(0,W.jsx)(X,{label:`Rejected`,value:U(h.RejectedAt)||`-`}),(0,W.jsx)(X,{label:`Version (optimistic lock)`,value:h.Version,mono:!0})]}),(0,W.jsx)(Wi,{label:`Decision reason`,children:h.DecisionReason?(0,W.jsx)(`p`,{className:`about-text`,children:h.DecisionReason}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`No decision yet`})}),(0,W.jsx)(Wi,{label:`Internal note · admins only`,children:h.InternalNote?(0,W.jsx)(`p`,{className:`about-text`,children:h.InternalNote}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not set`})})]})]})]}),side:(0,W.jsxs)(`section`,{className:`action-dock`,children:[(0,W.jsxs)(`div`,{className:`dock-title`,children:[(0,W.jsx)(ut,{size:14}),` `,`Decision`]}),!b&&!x&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This status has no available actions.`}),(b||x)&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Internal note`}),(0,W.jsx)(`textarea`,{value:i,onChange:e=>a(e.target.value),rows:3,placeholder:`Handover note for other admins`})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Optional. Stored with the decision and visible to admins only — never sent to the applicant.`})]}),b&&(0,W.jsxs)(W.Fragment,{children:[!_&&(0,W.jsx)(q,{children:`The verifier row is gone: its status was revoked after this application was filed. There is no icon to grant, so the application can only be rejected.`}),_&&!_.Enabled&&(0,W.jsx)(q,{children:`This verifier is disabled. It cannot mark anything new until an operator enables it again.`}),y&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This peer already carries this verifier's mark; approving refreshes the description and records the decision.`}),(0,W.jsxs)(`div`,{className:`action-stack`,children:[(0,W.jsx)(Z,{label:`Approve`,icon:(0,W.jsx)(F,{size:15}),tone:`neutral`,path:`/api/botverification/requests/${h.ID}/approve`,payload:E,onDone:D,onError:m}),(0,W.jsx)(Z,{label:`Reject`,icon:(0,W.jsx)(ne,{size:15}),tone:`warn`,path:`/api/botverification/requests/${h.ID}/reject`,payload:E,onDone:D,onError:m})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Puts the verifier's icon before the peer's name and its description in the profile, and messages the applicant.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The reason is mandatory: it is the wording the applicant is told, so write what exactly was missing.`})]}),x&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`div`,{className:`dock-title`,children:[(0,W.jsx)(st,{size:14}),` `,`Danger zone`]}),(0,W.jsxs)(`div`,{className:`danger-zone`,children:[(0,W.jsx)(Z,{label:`Revoke mark`,icon:(0,W.jsx)(he,{size:15}),tone:`danger`,path:`/api/botverification/requests/${h.ID}/revoke`,payload:E,onDone:D,onError:m}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Takes the icon and the description off the peer and closes the application as revoked. The official checkmark, if the peer has one, is untouched.`}),!y&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The peer carries no mark right now — revoking only closes the application.`})]})]})]})})]})}function Wi({label:e,children:t}){return(0,W.jsxs)(`div`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:e}),t]})}function Gi(e){return!e||!e.BotID||e.BotID===`0`?null:e}var Ki=[`draft`,`submitted`,`in_review`,`approved`,`rejected`,`cancelled`],qi=[`bot`,`channel`,`supergroup`,`user`],Ji={draft:`Draft`,submitted:`Submitted`,in_review:`In review`,approved:`Approved`,rejected:`Rejected`,cancelled:`Cancelled`},Yi={bot:`Bot`,channel:`Channel`,supergroup:`Supergroup`,user:`User`};function Xi({navigate:e}){let[t,n]=(0,g.useState)(`all`),[r,i]=(0,g.useState)(`all`),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(``),[l,u]=(0,g.useState)(`50`),[d,f]=(0,g.useState)([]),[p,m]=(0,g.useState)({}),[h,_]=(0,g.useState)(!1),[v,y]=(0,g.useState)(``),[b,x]=(0,g.useState)(!1),[S,C]=(0,g.useState)(``);async function w(e=!1){x(!0),C(``);let n=new URLSearchParams({limit:l});t!==`all`&&n.set(`status`,t),r!==`all`&&n.set(`target_type`,r),a.trim()&&n.set(`reviewer`,a.trim()),s.trim()&&n.set(`q`,s.trim().replace(/^@/,``)),e&&v&&n.set(`before_id`,v);try{let t=await k.verificationApplications(n),r=t.rows??[];f(t=>e?[...t,...r]:r),y(t.next_before_id??``),_(!!t.has_more)}catch(e){C(O(e))}finally{x(!1)}}async function T(){try{m((await k.verificationCounts()).counts??{})}catch(e){C(O(e))}}(0,g.useEffect)(()=>{w(!1),T()},[]);function E(){w(!1),T()}return(0,W.jsxs)(G,{title:`Verification queue`,eyebrow:`Verification / Queue`,actions:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:E,disabled:b,children:[(0,W.jsx)(B,{size:15,className:b?`spin`:``}),` `,`Refresh`]}),children:[S&&(0,W.jsx)(q,{children:S}),(0,W.jsx)(`div`,{className:`metric-row`,children:Ki.map(e=>(0,W.jsx)(Y,{label:Ji[e],value:p[e]??`0`,mono:!0,tone:$i(e,p[e]??`0`)},e))}),(0,W.jsx)(Lt,{children:(0,W.jsxs)(`form`,{className:`toolbar`,onSubmit:e=>{e.preventDefault(),w(!1)},children:[(0,W.jsxs)(`label`,{className:`searchbox`,children:[(0,W.jsx)(V,{size:15}),(0,W.jsx)(`input`,{value:s,onChange:e=>c(e.target.value),placeholder:`Application id, peer id, username or title`})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Status`}),(0,W.jsxs)(`select`,{value:t,onChange:e=>n(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All statuses`}),Ki.map(e=>(0,W.jsx)(`option`,{value:e,children:Ji[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Target type`}),(0,W.jsxs)(`select`,{value:r,onChange:e=>i(e.target.value),children:[(0,W.jsx)(`option`,{value:`all`,children:`All types`}),qi.map(e=>(0,W.jsx)(`option`,{value:e,children:Yi[e]},e))]})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Reviewer`}),(0,W.jsx)(`input`,{value:a,onChange:e=>o(e.target.value),placeholder:`Any reviewer`})]}),(0,W.jsxs)(`label`,{className:`field-inline`,children:[(0,W.jsx)(`span`,{children:`Limit`}),(0,W.jsx)(`input`,{className:`small-input`,value:l,onChange:e=>u(e.target.value),type:`number`,min:`1`,max:`200`})]}),(0,W.jsxs)(`button`,{className:`btn primary icon-text`,type:`submit`,disabled:b,children:[b?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(V,{size:15}),` `,`Search`]})]})}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`ID`}),(0,W.jsx)(`th`,{children:`Target`}),(0,W.jsx)(`th`,{children:`Applicant`}),(0,W.jsx)(`th`,{children:`Category`}),(0,W.jsx)(`th`,{children:`Status`}),(0,W.jsx)(`th`,{children:`Submitted`}),(0,W.jsx)(`th`,{children:`Reviewer`}),(0,W.jsx)(`th`,{})]})}),(0,W.jsxs)(`tbody`,{children:[d.map(t=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{className:`mono`,children:(0,W.jsxs)(`button`,{className:`row-link`,type:`button`,onClick:()=>e(`/verification/${t.ID}`),children:[`#`,t.ID]})}),(0,W.jsxs)(`td`,{children:[(0,W.jsx)(`strong`,{children:ea(t)}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[Yi[t.TargetType],` · `,t.TargetID]}),t.TargetVerified&&(0,W.jsxs)(J,{tone:`good`,children:[(0,W.jsx)(ee,{size:12}),` `,`Badge already on`]})]}),(0,W.jsxs)(`td`,{children:[H(t.ApplicantUsername)||t.ApplicantName||`-`,(0,W.jsx)(`div`,{className:`entity-subtitle mono`,children:t.ApplicantUserID})]}),(0,W.jsx)(`td`,{children:t.Category||`-`}),(0,W.jsx)(`td`,{children:(0,W.jsx)(Zi,{status:t.Status})}),(0,W.jsx)(`td`,{children:U(t.SubmittedAt)||`-`}),(0,W.jsx)(`td`,{children:t.ReviewerAdminID||`-`}),(0,W.jsx)(`td`,{children:(0,W.jsxs)(`button`,{className:`row-link`,type:`button`,onClick:()=>e(`/verification/${t.ID}`),children:[(0,W.jsx)(ot,{size:14}),` `,`Details`,` `,(0,W.jsx)(Se,{size:14})]})})]},t.ID)),d.length===0&&(0,W.jsx)(Bt,{colSpan:8})]})]})}),h&&(0,W.jsx)(`div`,{className:`toolbar`,children:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>w(!0),disabled:b,children:[b?(0,W.jsx)(I,{size:15,className:`spin`}):(0,W.jsx)(be,{size:15}),` `,`Load more`]})})]})}function Zi({status:e}){return(0,W.jsx)(J,{tone:Qi(e),children:Ji[e]})}function Qi(e){return e===`approved`?`good`:e===`submitted`||e===`in_review`?`warn`:e===`rejected`?`danger`:`neutral`}function $i(e,t){return e===`submitted`||e===`in_review`?t!==`0`&&t!==``?`warn`:`neutral`:e===`approved`?`good`:`neutral`}function ea(e){return H(e.TargetUsername)||e.TargetTitle||`#${e.TargetID}`}function ta(e){return e.TargetType===`bot`?`/bots/${e.TargetID}`:e.TargetType===`user`?`/accounts/${e.TargetID}`:`/channels/${e.TargetID}`}var na={created:`Created`,updated:`Updated`,submitted:`Submitted`,claimed:`Claimed`,approved:`Approved`,rejected:`Rejected`,cancelled:`Cancelled`,revoked:`Badge revoked`,notified:`Applicant notified`};function ra({id:e,navigate:t}){let{can:n}=Xt(),[r,i]=(0,g.useState)(null),[a,o]=(0,g.useState)(``),[s,c]=(0,g.useState)(!1),[l,u]=(0,g.useState)(!1),[d,f]=(0,g.useState)(``);async function p(){u(!0),f(``);try{i(await k.verificationApplication(e))}catch(e){f(O(e))}finally{u(!1)}}function m(){c(!1),p()}(0,g.useEffect)(()=>{p()},[e]);function h(e){if(e instanceof v&&e.status===409)return c(!0),p(),`Another admin has already changed this application. The data has been reloaded — check the status before deciding again.`}if(d&&!r)return(0,W.jsx)(q,{children:d});if(!r)return(0,W.jsx)(Vt,{label:`Loading the application…`});let _=r.application,y=r.events??[],b=r.applicant_controls_target,x=r.target_verified,S=_.Status===`submitted`,C=_.Status===`submitted`||_.Status===`in_review`,w=_.Status===`approved`&&n(`verification.revoke`),T=a.trim();function E(){let e={version:_.Version};return T&&(e.internal_note=T),e}function D(){o(``),c(!1),p()}return(0,W.jsxs)(G,{title:`Application #${_.ID}`,eyebrow:`Verification / Review`,actions:(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/verification`),children:[(0,W.jsx)(fe,{size:15}),` `,`Back to list`]}),(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:m,disabled:l,children:[(0,W.jsx)(B,{size:15,className:l?`spin`:``}),` `,`Refresh`]})]}),children:[d&&(0,W.jsx)(q,{children:d}),s&&(0,W.jsx)(q,{children:`Another admin has already changed this application. The data has been reloaded — check the status before deciding again.`}),(0,W.jsx)(Rt,{main:(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`section`,{className:`entity-head`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`entity-title`,children:ea(_)}),(0,W.jsxs)(`div`,{className:`entity-subtitle mono`,children:[`#`,_.ID,` · `,Yi[_.TargetType],`:`,_.TargetID,` · v`,_.Version]})]}),(0,W.jsxs)(`div`,{className:`entity-badges`,children:[(0,W.jsx)(Zi,{status:_.Status}),x&&(0,W.jsxs)(J,{tone:`good`,children:[(0,W.jsx)(ee,{size:12}),` `,`Badge already on`]}),(0,W.jsx)(J,{tone:b?`good`:`danger`,children:b?`Control confirmed`:`No control over the target`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Target`,text:`The peer the badge would be attached to, as it exists right now.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(ta(_)),children:[(0,W.jsx)(De,{size:15}),` `,`Open target`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Type`,value:Yi[_.TargetType]}),(0,W.jsx)(X,{label:`Username`,value:H(_.TargetUsername)||`-`}),(0,W.jsx)(X,{label:`Title`,value:_.TargetTitle||`-`}),(0,W.jsx)(X,{label:`Peer ID`,value:_.TargetID,mono:!0})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Applicant`,text:`Who filed the application and whether they still hold rights on the target.`,action:(0,W.jsxs)(`button`,{className:`btn icon-text`,type:`button`,onClick:()=>t(`/accounts/${_.ApplicantUserID}`),children:[(0,W.jsx)(gt,{size:15}),` `,`Open account`]})}),(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Username`,value:H(_.ApplicantUsername)||`-`}),(0,W.jsx)(X,{label:`Name`,value:_.ApplicantName||`-`}),(0,W.jsx)(X,{label:`User ID`,value:_.ApplicantUserID,mono:!0}),(0,W.jsx)(X,{label:`Submitted`,value:U(_.SubmittedAt)||`-`})]}),b?(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The applicant controls the target right now — checked against the live records, not against the submission snapshot.`}):(0,W.jsx)(q,{children:`The applicant no longer controls the target. Approving would hand the badge to someone who does not hold the peer — normally a reason to reject.`})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Application`,text:`Everything the applicant submitted, rendered as plain text.`}),(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Category`,value:_.Category||`-`}),(0,W.jsx)(X,{label:`Correlation ID`,value:_.CorrelationID||`-`,mono:!0}),(0,W.jsx)(X,{label:`Created`,value:U(_.CreatedAt)||`-`}),(0,W.jsx)(X,{label:`Updated`,value:U(_.UpdatedAt)||`-`})]}),(0,W.jsx)(ia,{label:`Description`,children:_.Description?(0,W.jsx)(`p`,{className:`about-text`,children:_.Description}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`})}),(0,W.jsx)(ia,{label:`Official website`,children:_.OfficialWebsite?(0,W.jsx)(`div`,{className:`about-text`,children:(0,W.jsx)(aa,{value:_.OfficialWebsite})}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`})}),(0,W.jsx)(ia,{label:`Social links`,children:(0,W.jsx)(oa,{values:_.SocialLinks})}),(0,W.jsx)(ia,{label:`Press coverage`,children:(0,W.jsx)(oa,{values:_.PressLinks})}),(0,W.jsx)(ia,{label:`Applicant comment`,children:_.AdditionalNote?(0,W.jsx)(`p`,{className:`about-text`,children:_.AdditionalNote}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Only http:// and https:// links are clickable and open in a new tab; anything else is shown as text.`})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`Decision`,text:`What was decided, by whom, and with which wording.`}),(0,W.jsxs)(`div`,{className:`stacked-sections`,children:[(0,W.jsxs)(`div`,{className:`summary-grid`,children:[(0,W.jsx)(X,{label:`Reviewer`,value:_.ReviewerAdminID||`-`}),(0,W.jsx)(X,{label:`Decided`,value:U(_.ReviewedAt)||`-`}),(0,W.jsx)(X,{label:`Status`,value:Ji[_.Status]}),(0,W.jsx)(X,{label:`Version (optimistic lock)`,value:_.Version,mono:!0})]}),(0,W.jsx)(ia,{label:`Decision reason`,children:_.DecisionReason?(0,W.jsx)(`p`,{className:`about-text`,children:_.DecisionReason}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`No decision yet`})}),(0,W.jsx)(ia,{label:`Internal note · admins only`,children:_.InternalNote?(0,W.jsx)(`p`,{className:`about-text`,children:_.InternalNote}):(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`})})]})]}),(0,W.jsxs)(`section`,{className:`section-block`,children:[(0,W.jsx)(K,{title:`History`,text:`Immutable trail of every status transition, with actor and reason.`}),(0,W.jsx)(`div`,{className:`table-wrap`,children:(0,W.jsxs)(`table`,{className:`data-table`,children:[(0,W.jsx)(`thead`,{children:(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`th`,{children:`Event`}),(0,W.jsx)(`th`,{children:`From → to`}),(0,W.jsx)(`th`,{children:`Actor`}),(0,W.jsx)(`th`,{children:`Reason`}),(0,W.jsx)(`th`,{children:`Internal note`}),(0,W.jsx)(`th`,{children:`Time`})]})}),(0,W.jsxs)(`tbody`,{children:[y.map(e=>(0,W.jsxs)(`tr`,{children:[(0,W.jsx)(`td`,{children:(0,W.jsx)(sa,{kind:e.Kind})}),(0,W.jsxs)(`td`,{className:`mono`,children:[e.FromStatus||`-`,` → `,e.ToStatus||`-`]}),(0,W.jsx)(`td`,{children:e.Actor||`-`}),(0,W.jsx)(`td`,{className:`truncate`,children:e.Reason||`-`}),(0,W.jsx)(`td`,{className:`truncate`,children:e.Note||`-`}),(0,W.jsx)(`td`,{children:U(e.CreatedAt)||`-`})]},e.ID)),y.length===0&&(0,W.jsx)(Bt,{colSpan:6})]})]})})]})]}),side:(0,W.jsxs)(`section`,{className:`action-dock`,children:[(0,W.jsx)(`div`,{className:`dock-title`,children:`Review actions`}),!S&&!C&&!w&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`This status has no available actions.`}),S&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`div`,{className:`action-stack`,children:(0,W.jsx)(Z,{label:`Take into review`,icon:(0,W.jsx)(Ne,{size:15}),tone:`neutral`,path:`/api/verification/applications/${_.ID}/claim`,payload:()=>({version:_.Version}),onDone:D,onError:h})}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Assigns the application to you and moves it to in review, so two reviewers never work on the same one.`})]}),(C||w)&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`label`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:`Internal note`}),(0,W.jsx)(`textarea`,{value:a,onChange:e=>o(e.target.value),rows:3,placeholder:`Handover note for other reviewers`})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Optional. Stored with the decision and visible to admins only — never sent to the applicant.`})]}),C&&(0,W.jsxs)(W.Fragment,{children:[!b&&(0,W.jsx)(q,{children:`The applicant no longer controls the target. Approving would hand the badge to someone who does not hold the peer — normally a reason to reject.`}),x&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The target already carries the badge; approving only records the decision.`}),(0,W.jsxs)(`div`,{className:`action-stack`,children:[(0,W.jsx)(Z,{label:`Approve`,icon:(0,W.jsx)(F,{size:15}),tone:`neutral`,path:`/api/verification/applications/${_.ID}/approve`,payload:E,onDone:D,onError:h}),(0,W.jsx)(Z,{label:`Reject`,icon:(0,W.jsx)(ne,{size:15}),tone:`warn`,path:`/api/verification/applications/${_.ID}/reject`,payload:E,onDone:D,onError:h})]}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Grants the official badge to the target and closes the application.`}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The reason is mandatory: it is the wording the applicant is told, so write what exactly was missing.`})]}),w&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`div`,{className:`dock-title`,children:[(0,W.jsx)(st,{size:14}),` `,`Danger zone`]}),(0,W.jsxs)(`div`,{className:`danger-zone`,children:[(0,W.jsx)(Z,{label:`Revoke verification`,icon:(0,W.jsx)(he,{size:15}),tone:`danger`,path:`/api/actions/revoke-verification`,payload:()=>{let e={target_type:_.TargetType,target_id:_.TargetID};return T&&(e.internal_note=T),e},onDone:D,onError:h}),(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Clears the badge from the target. The approved application stays in history.`}),!x&&(0,W.jsx)(`p`,{className:`bot-create-note`,children:`The target carries no badge right now — there is nothing to revoke.`})]})]})]})})]})}function ia({label:e,children:t}){return(0,W.jsxs)(`div`,{className:`duration-field`,children:[(0,W.jsx)(`span`,{children:e}),t]})}function aa({value:e}){let t=Tt(e);return t?(0,W.jsxs)(`a`,{className:`row-link`,href:t,target:`_blank`,rel:`noopener noreferrer`,children:[e,` `,(0,W.jsx)(De,{size:13})]}):(0,W.jsx)(`span`,{className:`mono`,children:e})}function oa({values:e}){let t=(e??[]).filter(e=>e.trim()!==``);return t.length===0?(0,W.jsx)(`p`,{className:`bot-create-note`,children:`Not provided`}):(0,W.jsx)(`div`,{className:`about-text`,children:t.map((e,t)=>(0,W.jsx)(`div`,{children:(0,W.jsx)(aa,{value:e})},`${t}-${e}`))})}function sa({kind:e}){return(0,W.jsx)(J,{tone:e===`approved`?`good`:e===`rejected`||e===`revoked`||e===`cancelled`?`danger`:e===`submitted`||e===`claimed`?`warn`:`neutral`,children:na[e]})}function ca({route:e,navigate:t}){let n=e.path.match(/^\/accounts\/(\d+)$/)?.[1],r=e.path.match(/^\/channels\/(\d+)$/)?.[1],i=e.path.match(/^\/bots\/(\d+)$/)?.[1],a=e.path.match(/^\/moderation\/(\d+)$/)?.[1],o=e.path.match(/^\/collectible-usernames\/(\d+)$/)?.[1],s=e.path.match(/^\/verification\/(\d+)$/)?.[1],c=e.path.match(/^\/bot-verification\/(\d+)$/)?.[1];return c?(0,W.jsx)(tn,{children:(0,W.jsx)($t,{permission:Gt,children:(0,W.jsx)(Ui,{id:c,navigate:t})})}):e.path===`/bot-verification`?(0,W.jsx)(tn,{children:(0,W.jsx)($t,{permission:Gt,children:(0,W.jsx)(Mi,{navigate:t})})}):s?(0,W.jsx)($t,{permission:Wt,children:(0,W.jsx)(ra,{id:s,navigate:t})}):e.path===`/verification`?(0,W.jsx)($t,{permission:Wt,children:(0,W.jsx)(Xi,{navigate:t})}):o?(0,W.jsx)(er,{id:o,navigate:t}):e.path===`/collectible-usernames`?(0,W.jsx)(Xn,{navigate:t}):e.path===`/storage`?(0,W.jsx)(di,{navigate:t}):n?(0,W.jsx)(Rn,{id:Number(n),navigate:t}):r?(0,W.jsx)(ir,{id:Number(r),navigate:t}):i?(0,W.jsx)(cr,{id:Number(i),navigate:t}):a?(0,W.jsx)(ii,{id:Number(a),navigate:t}):e.path===`/accounts/shared-devices`?(0,W.jsx)(Wn,{navigate:t}):e.path===`/accounts`?(0,W.jsx)(Un,{navigate:t}):e.path===`/channels`?(0,W.jsx)(or,{navigate:t}):e.path===`/bots`?(0,W.jsx)(dr,{navigate:t}):e.path===`/moderation`?(0,W.jsx)(Zr,{navigate:t}):e.path===`/broadcasts`?(0,W.jsx)(mr,{}):e.path===`/emoji`?(0,W.jsx)(Or,{kind:`emoji`}):e.path===`/stickers`?(0,W.jsx)(Or,{kind:`stickers`}):e.path===`/gif-catalog`?(0,W.jsx)(jr,{}):e.path===`/server-settings`?(0,W.jsx)($t,{permission:qt,children:(0,W.jsx)(Nr,{})}):e.path===`/messages/detail`||e.path===`/messages/private/detail`?(0,W.jsx)(br,{ownerUserID:Number(e.search.get(`owner_user_id`)||`0`),msgID:Number(e.search.get(`msg_id`)||`0`),navigate:t}):e.path===`/messages/groups/detail`?(0,W.jsx)(Q,{channelID:Number(e.search.get(`channel_id`)||`0`),msgID:Number(e.search.get(`msg_id`)||`0`),navigate:t}):e.path===`/messages/groups`?(0,W.jsx)(yr,{navigate:t}):e.path===`/messages`||e.path===`/messages/private`?(0,W.jsx)(xr,{navigate:t}):(0,W.jsx)(hr,{navigate:t})}function la(){let[e,t]=(0,g.useState)(void 0),[n,r]=(0,g.useState)(()=>nn());(0,g.useEffect)(()=>{let e=()=>r(nn());return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},[]),(0,g.useEffect)(()=>{k.session().then(e=>t(e)).catch(()=>t(null))},[]);let i=e=>{window.history.pushState(null,``,e),r(nn())};return e===void 0?(0,W.jsx)(_n,{}):e===null?(0,W.jsx)(bn,{onLogin:t}):(0,W.jsx)(Yt,{permissions:e.permissions??[],hideThirdPartyVerification:e.hide_third_party_verification??!0,children:(0,W.jsx)(vn,{actor:e.actor,apiLayers:e.api_layers,build:e.build,route:n,navigate:i,onLogout:()=>t(null),children:(0,W.jsx)(ca,{route:n,navigate:i})})})}_.createRoot(document.getElementById(`root`)).render((0,W.jsx)(g.StrictMode,{children:(0,W.jsx)(cn,{children:(0,W.jsx)(la,{})})})); \ No newline at end of file diff --git a/cmd/telesrv-admin/web/dist/index.html b/cmd/telesrv-admin/web/dist/index.html index 64013f42..56e7c24c 100644 --- a/cmd/telesrv-admin/web/dist/index.html +++ b/cmd/telesrv-admin/web/dist/index.html @@ -23,7 +23,7 @@ })(); - + diff --git a/cmd/telesrv-admin/web/src/pages/StoragePage.tsx b/cmd/telesrv-admin/web/src/pages/StoragePage.tsx index 96c9406f..e1a706b2 100644 --- a/cmd/telesrv-admin/web/src/pages/StoragePage.tsx +++ b/cmd/telesrv-admin/web/src/pages/StoragePage.tsx @@ -1,5 +1,6 @@ -import { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown, Loader2, RefreshCw, Search } from "lucide-react"; +import { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown, Loader2, RefreshCw, Search, Settings2, X } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; +import { createPortal } from "react-dom"; import { api, errorMessage } from "../api"; import { ActionButton } from "../components/ActionButton"; import { Alert, EmptyRow, Metric, PageFrame, QueryPanel, SectionHead } from "../components/ui"; @@ -200,13 +201,28 @@ export function StoragePage({ navigate: _navigate }: { navigate: Navigate }) { // GB inputs for byte budgets, a mode dropdown + day count for retention. const STORAGE_ENV_KEYS = { + blobBackend: "TELESRV_BLOB_BACKEND", maxTotal: "TELESRV_STORAGE_MAX_TOTAL_BYTES", minFree: "TELESRV_STORAGE_MIN_FREE_BYTES", maxUploadFile: "TELESRV_STORAGE_MAX_UPLOAD_FILE_BYTES", retentionMode: "TELESRV_STORAGE_RETENTION_MODE", - retentionMaxAge: "TELESRV_STORAGE_RETENTION_MAX_AGE" + retentionMaxAge: "TELESRV_STORAGE_RETENTION_MAX_AGE", + evictionEnable: "TELESRV_STORAGE_EVICTION_ENABLE" } as const; +// Per-category retention age overrides -- each optional, empty/0 means +// "inherit the shared Retention age" above. Order matches the admin UI list. +const CATEGORY_AGE_FIELDS: { key: string; envKey: string; label: string }[] = [ + { key: "photo", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_PHOTO", label: "Photo" }, + { key: "video", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_VIDEO", label: "Video" }, + { key: "round_video", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_ROUND_VIDEO", label: "Round video (video message)" }, + { key: "gif", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_GIF", label: "GIF" }, + { key: "music", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_MUSIC", label: "Music" }, + { key: "voice", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_VOICE", label: "Voice message" }, + { key: "file", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_FILE", label: "File" }, + { key: "avatar", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_AVATAR", label: "Avatar" } +]; + // Mirrors internal/app/files.MaxUploadPartBytes * MaxUploadParts -- the // protocol's own upload-part-count ceiling that TELESRV_STORAGE_MAX_UPLOAD_FILE_BYTES // can never legally exceed (internal/config validates this server-side too; @@ -272,11 +288,23 @@ function DurationField({ const totalMinutes = Number(minutes || "0"); const [unitLabel, setUnitLabel] = useState(() => bestDurationUnit(totalMinutes).label); const unit = DURATION_UNITS.find((u) => u.label === unitLabel) ?? DURATION_UNITS[2]; - const amount = totalMinutes > 0 ? totalMinutes / unit.minutes : NaN; + // 0 is a real, distinct value here (e.g. the shared Retention age + // deliberately set to 0 to disable the default sweep) -- unlike + // ByteSizeField's "0/empty = Unlimited" convention, this must not collapse + // to a blank input, or there'd be no way to tell "0" from "not typed yet". + const amount = Number.isFinite(totalMinutes) ? totalMinutes / unit.minutes : NaN; function handleAmountChange(raw: string) { + if (!raw.trim()) { + onChange("0"); + return; + } const parsed = Number(raw); - if (!raw.trim() || Number.isNaN(parsed) || parsed <= 0) { + if (Number.isNaN(parsed) || parsed < 0) { + onChange("0"); + return; + } + if (parsed === 0) { onChange("0"); return; } @@ -292,6 +320,7 @@ function DurationField({ min="0" step="any" value={Number.isNaN(amount) ? "" : amount} + placeholder={"0"} disabled={disabled} onChange={(event) => handleAmountChange(event.target.value)} /> @@ -357,15 +386,71 @@ function ByteSizeField({ ); } +// CategoryRetentionModal is a focused editor for the per-category age +// overrides -- pulled out of the main Limits & Retention flow (which was +// getting crowded with 8 extra duration fields) into its own dialog, following +// the same modal-backdrop/command-modal pattern as MintCollectibleUsernameModal. +// It edits the same categoryAgeMinutes state the parent already owns; there's +// no separate save here, just Close -- the one shared "Save limits & +// retention settings" button below still covers these values too. +function CategoryRetentionModal({ + categoryAgeMinutes, + onChange, + disabled, + onClose +}: { + categoryAgeMinutes: Record; + onChange: (key: string, minutes: string) => void; + disabled: boolean; + onClose: () => void; +}) { + return createPortal( +
+
+
+
+
{"Limits & Retention"}
+

{"Per-category overrides"}

+
+ +
+
+

+ {"Leave a category at 0 to inherit the shared Retention age. The mode switch still applies to all of them -- these only change how old that one category's media must be."} +

+ {CATEGORY_AGE_FIELDS.map((field) => ( + onChange(field.key, minutes)} + /> + ))} +
+
+ +
+
+
, + document.body + ); +} + function LimitsRetentionSection() { const [loaded, setLoaded] = useState(false); const [error, setError] = useState(""); const [initial, setInitial] = useState>({}); + const [blobBackend, setBlobBackend] = useState("s3"); const [maxTotalBytes, setMaxTotalBytes] = useState("0"); const [minFreeBytes, setMinFreeBytes] = useState("0"); const [maxUploadFileBytes, setMaxUploadFileBytes] = useState("0"); const [retentionMode, setRetentionMode] = useState("off"); const [retentionAgeMinutes, setRetentionAgeMinutes] = useState("43200"); + const [categoryAgeMinutes, setCategoryAgeMinutes] = useState>({}); + const [categoryModalOpen, setCategoryModalOpen] = useState(false); + const [evictionEnable, setEvictionEnable] = useState(false); async function load() { setError(""); @@ -378,6 +463,7 @@ function LimitsRetentionSection() { } } setInitial(values); + setBlobBackend((values[STORAGE_ENV_KEYS.blobBackend] || "s3").trim().toLowerCase()); setMaxTotalBytes(values[STORAGE_ENV_KEYS.maxTotal] || "0"); setMinFreeBytes(values[STORAGE_ENV_KEYS.minFree] || "0"); setMaxUploadFileBytes(values[STORAGE_ENV_KEYS.maxUploadFile] || "0"); @@ -385,6 +471,14 @@ function LimitsRetentionSection() { setRetentionMode(mode === "orphan" || mode === "hard" ? mode : "off"); const mins = parseDurationMinutes(values[STORAGE_ENV_KEYS.retentionMaxAge] || "720h"); setRetentionAgeMinutes(mins > 0 ? String(Math.max(1, Math.round(mins))) : "43200"); + const nextCategoryAges: Record = {}; + for (const field of CATEGORY_AGE_FIELDS) { + const raw = values[field.envKey] || ""; + const categoryMins = parseDurationMinutes(raw); + nextCategoryAges[field.key] = categoryMins > 0 ? String(Math.round(categoryMins)) : "0"; + } + setCategoryAgeMinutes(nextCategoryAges); + setEvictionEnable((values[STORAGE_ENV_KEYS.evictionEnable] || "false").trim().toLowerCase() === "true"); setLoaded(true); } catch (err) { setError(errorMessage(err)); @@ -404,14 +498,26 @@ function LimitsRetentionSection() { [STORAGE_ENV_KEYS.retentionMode]: retentionMode, [STORAGE_ENV_KEYS.retentionMaxAge]: retentionMode === "off" ? (initial[STORAGE_ENV_KEYS.retentionMaxAge] || "720h") - : `${Math.max(1, Math.round(Number(retentionAgeMinutes || "0")))}m` + // 0 is a legitimate value here: "no sweep by default", left for + // per-category overrides below to opt specific categories in. Don't + // clamp it up to a minimum of 1 -- that used to make it impossible to + // ever save a 0 global age. + : `${Math.max(0, Math.round(Number(retentionAgeMinutes || "0")))}m`, + [STORAGE_ENV_KEYS.evictionEnable]: evictionEnable ? "true" : "false" }; + // Per-category overrides: a field left at "inherit global" (0) saves as + // empty rather than a redundant explicit duration, matching the "unset + // means inherit" contract on the server side. + for (const field of CATEGORY_AGE_FIELDS) { + const mins = Math.round(Number(categoryAgeMinutes[field.key] || "0")); + next[field.envKey] = mins > 0 ? `${mins}m` : ""; + } const changed: Record = {}; for (const [key, value] of Object.entries(next)) { if ((initial[key] ?? "") !== value) changed[key] = value; } return changed; - }, [maxTotalBytes, minFreeBytes, maxUploadFileBytes, retentionMode, retentionAgeMinutes, initial]); + }, [maxTotalBytes, minFreeBytes, maxUploadFileBytes, retentionMode, retentionAgeMinutes, categoryAgeMinutes, evictionEnable, initial]); const hasChanges = Object.keys(pendingValues).length > 0; const uploadCeilingExceeded = Number(maxUploadFileBytes || "0") > PROTOCOL_UPLOAD_CEILING_BYTES; @@ -434,12 +540,14 @@ function LimitsRetentionSection() { bytes={maxTotalBytes} onChange={setMaxTotalBytes} /> - + {blobBackend === "localfs" && ( + + )} +
+ + + {"Optional -- give Photo/Video/GIF/Music/Voice/File/Avatar their own retention age instead of the shared one above."} + +
+ {categoryModalOpen && ( + setCategoryAgeMinutes((prev) => ({ ...prev, [key]: minutes }))} + disabled={retentionMode === "off"} + onClose={() => setCategoryModalOpen(false)} + /> + )} + +
+ +

+ {"Once total physical storage exceeds the Max total storage budget above, actively delete the oldest files (regardless of category or age) until back under budget -- the same way \"hard\" retention mode purges files. Independent of the retention mode above: this can run even when that's Off. Off by default, since this changes the storage budget from block-new-uploads-only to also reclaiming from existing files."} +

+
+
override asks for a much + // shorter age (or, if the shared default is 0 -- "disabled by default, + // only specific categories opt in" -- would disable the sweep outright, + // since a 0 maxAge here used to gate the whole sweep off). Use the + // fastest positive age across the shared default and every override + // instead, so the ticker -- and the sweep-enabled gate -- reflect + // whatever is actually configured to run soonest. + fastestRetentionAge := fastestPositiveDuration(cfg.StorageRetentionMaxAge, cfg.StorageRetentionMaxAgeAvatar) + for _, age := range cfg.StorageRetentionMaxAgeByCategory { + fastestRetentionAge = fastestPositiveDuration(fastestRetentionAge, age) + } switch cfg.StorageRetentionMode { case config.StorageRetentionModeOrphan: - retentionWorker = retentionWorker.WithOrphanedMediaRetention(filesService, cfg.StorageRetentionMaxAge) + retentionWorker = retentionWorker.WithOrphanedMediaRetention(filesService, fastestRetentionAge) case config.StorageRetentionModeHard: - retentionWorker = retentionWorker.WithHardMediaRetention(filesService, cfg.StorageRetentionMaxAge) + retentionWorker = retentionWorker.WithHardMediaRetention(filesService, fastestRetentionAge) } + // Active eviction is independent of TELESRV_STORAGE_RETENTION_MODE (can + // run even when that's "off") and reuses the same media sweep ticker. + retentionWorker = retentionWorker.WithStorageEviction(filesService, cfg.StorageEvictionEnable) go retentionWorker.Run(ctx) go filesapp.NewUploadPartGCWorker(filesService, logger.Named("files").Named("upload_gc"), cfg.UploadPartTTL, @@ -1383,6 +1423,13 @@ func run(logger *zap.Logger) error { messageapp.WithSendPermissionChecker(adminService), messageapp.WithBusinessAutomation(passwordStore, businessAutomationOptions...), ) + // Wires the storage retention sweep's purge-notice capability now that + // both edit-capable app services exist -- filesService (and the + // background retentionWorker goroutine reading it) was constructed + // earlier, before either was available. A sweep tick that races ahead of + // this call simply finds no notifier yet and skips the notice for that + // tick (best-effort, see files.SetRetentionPurgeNotifier). + filesService.SetRetentionPurgeNotifier(messagesService, channelsService) moderationService := moderationapp.NewService( moderationReportStore, moderationapp.WithMessageReaders(messagesService, channelsService), diff --git a/cmd/telesrv/main_test.go b/cmd/telesrv/main_test.go index 4850bcf9..2c5d08d0 100644 --- a/cmd/telesrv/main_test.go +++ b/cmd/telesrv/main_test.go @@ -2,10 +2,43 @@ package main import ( "testing" + "time" telegramloginapp "telesrv/internal/app/telegramlogin" ) +// TestFastestPositiveDurationIgnoresNonPositiveValues guards a real reported +// bug: the storage retention sweep's ticker cadence used to be driven purely +// by the shared TELESRV_STORAGE_RETENTION_MAX_AGE default (e.g. 30 days), +// even when a much shorter TELESRV_STORAGE_RETENTION_MAX_AGE_ +// override was configured -- so a category set to "1 minute" would still +// only actually get swept on the shared default's own slow cadence (or, if +// the shared default was 0, would disable the sweep outright). The worker +// must be handed the fastest positive age across the default and every +// override instead. +func TestFastestPositiveDurationIgnoresNonPositiveValues(t *testing.T) { + cases := []struct { + name string + a, b time.Duration + want time.Duration + }{ + {"both positive, a smaller", 30 * 24 * time.Hour, time.Minute, time.Minute}, + {"both positive, b smaller", time.Minute, 30 * 24 * time.Hour, time.Minute}, + {"a zero (disabled), b positive", 0, time.Minute, time.Minute}, + {"a positive, b zero (disabled)", time.Minute, 0, time.Minute}, + {"a negative, b positive", -time.Hour, time.Minute, time.Minute}, + {"both zero (nothing configured)", 0, 0, 0}, + {"both negative", -time.Hour, -time.Minute, -time.Minute}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := fastestPositiveDuration(c.a, c.b); got != c.want { + t.Fatalf("fastestPositiveDuration(%v, %v) = %v, want %v", c.a, c.b, got, c.want) + } + }) + } +} + func TestTelegramLoginRPCDependencyPreservesDisabledNil(t *testing.T) { var disabled *telegramloginapp.Service if dependency := telegramLoginRPCDependency(disabled); dependency != nil { diff --git a/deploy/migrations/20260902000001_documents_category.down.sql b/deploy/migrations/20260902000001_documents_category.down.sql new file mode 100644 index 00000000..a118c686 --- /dev/null +++ b/deploy/migrations/20260902000001_documents_category.down.sql @@ -0,0 +1,3 @@ +DROP INDEX IF EXISTS public.documents_category_created_at_idx; +ALTER TABLE public.documents + DROP COLUMN IF EXISTS category; diff --git a/deploy/migrations/20260902000001_documents_category.up.sql b/deploy/migrations/20260902000001_documents_category.up.sql new file mode 100644 index 00000000..79f8d675 --- /dev/null +++ b/deploy/migrations/20260902000001_documents_category.up.sql @@ -0,0 +1,18 @@ +-- Adds a per-document media category column so the storage retention sweep +-- can use a different retention age per category (Photo/Video/RoundVideo/ +-- Gif/Music/Voice/File/Avatar -- see TELESRV_STORAGE_RETENTION_MAX_AGE_*) +-- instead of one shared age for every document regardless of kind. +-- +-- The value mirrors domain.MediaCategory (int16): 0 = None/unclassified +-- (stickers and anything else classifyDocumentCategory doesn't tag -- these +-- always fall back to the shared global age, there is no per-category +-- override for this bucket), 2 = Video, 3 = Gif, 4 = File, 5 = Music, +-- 6 = Voice, 7 = RoundVideo. Deliberately NOT backfilled for existing rows: +-- they stay at the default 0 (global age) until they are next +-- created/edited, which is an acceptable one-time transitional behavior -- +-- backfilling would require re-deriving the category from each document's +-- already-stored attributes for potentially every row in the table. +ALTER TABLE public.documents + ADD COLUMN category smallint NOT NULL DEFAULT 0; + +CREATE INDEX documents_category_created_at_idx ON public.documents (category, created_at); diff --git a/deploy/migrations/20260903000001_account_settings_disallowed_gifts.down.sql b/deploy/migrations/20260903000001_account_settings_disallowed_gifts.down.sql new file mode 100644 index 00000000..a5cfd6f4 --- /dev/null +++ b/deploy/migrations/20260903000001_account_settings_disallowed_gifts.down.sql @@ -0,0 +1,6 @@ +ALTER TABLE public.account_settings + DROP COLUMN IF EXISTS disallow_unlimited_stargifts, + DROP COLUMN IF EXISTS disallow_limited_stargifts, + DROP COLUMN IF EXISTS disallow_unique_stargifts, + DROP COLUMN IF EXISTS disallow_premium_gifts, + DROP COLUMN IF EXISTS disallow_stargifts_from_channels; diff --git a/deploy/migrations/20260903000001_account_settings_disallowed_gifts.up.sql b/deploy/migrations/20260903000001_account_settings_disallowed_gifts.up.sql new file mode 100644 index 00000000..373ac9e1 --- /dev/null +++ b/deploy/migrations/20260903000001_account_settings_disallowed_gifts.up.sql @@ -0,0 +1,15 @@ +-- account_settings gained global gift-reception switches (Layer 228 +-- disallowedGifts) in code (internal/domain/account.go's DisallowedGifts, +-- read/written by internal/store/postgres/account.go's GetAccountSettings/ +-- GetAccountSettingsBatch/SaveAccountSettings) without a matching migration +-- ever being added -- every account_settings read/write has been failing +-- with "column does not exist" on any database that never got these columns, +-- which breaks not just gift settings but every RPC that loads cached +-- account settings (privacy, account TTL, contact sign-up notification, +-- sendMessage's privacy check, etc). +ALTER TABLE public.account_settings + ADD COLUMN IF NOT EXISTS disallow_unlimited_stargifts boolean NOT NULL DEFAULT false, + ADD COLUMN IF NOT EXISTS disallow_limited_stargifts boolean NOT NULL DEFAULT false, + ADD COLUMN IF NOT EXISTS disallow_unique_stargifts boolean NOT NULL DEFAULT false, + ADD COLUMN IF NOT EXISTS disallow_premium_gifts boolean NOT NULL DEFAULT false, + ADD COLUMN IF NOT EXISTS disallow_stargifts_from_channels boolean NOT NULL DEFAULT false; diff --git a/internal/app/channels/service.go b/internal/app/channels/service.go index 40815330..f6ddba08 100644 --- a/internal/app/channels/service.go +++ b/internal/app/channels/service.go @@ -1514,6 +1514,22 @@ func (s *Service) EditMessage(ctx context.Context, userID int64, req domain.Edit return s.channels.EditChannelMessage(ctx, req) } +// EditChannelMessageInternal performs a server-internal channel message edit +// with no acting user -- currently only the storage retention sweep +// (internal/app/files.notifyRetentionPurge), which needs to turn a purged +// message into a visible notice but has no RPC caller/channel member behind +// it. Bypasses the userID==0 gate EditMessage enforces for ordinary +// RPC-driven edits; the store's own RetentionPurge bypass (see +// EditChannelMessageRequest.RetentionPurge) enforces the actual permission +// bypass semantics, so this refuses anything that isn't actually a retention +// purge request rather than becoming a second general-purpose edit path. +func (s *Service) EditChannelMessageInternal(ctx context.Context, req domain.EditChannelMessageRequest) (domain.EditChannelMessageResult, error) { + if s == nil || s.channels == nil || !req.RetentionPurge || req.RetentionPurgeAction == nil || req.ChannelID == 0 || req.ID <= 0 { + return domain.EditChannelMessageResult{}, domain.ErrChannelInvalid + } + return s.channels.EditChannelMessage(ctx, req) +} + // GetInlineBotMessage returns one live channel message addressed by a signed inline id. func (s *Service) GetInlineBotMessage(ctx context.Context, botID, channelID int64, id int) (domain.Channel, domain.ChannelMessage, bool, error) { if s == nil || s.channels == nil || botID == 0 || channelID == 0 || id <= 0 || id > domain.MaxMessageBoxID { diff --git a/internal/app/files/retention.go b/internal/app/files/retention.go index c0af94d8..9769adbf 100644 --- a/internal/app/files/retention.go +++ b/internal/app/files/retention.go @@ -3,6 +3,7 @@ package files import ( "context" "fmt" + "sort" "time" "go.uber.org/zap" @@ -10,14 +11,52 @@ import ( "telesrv/internal/domain" ) +// hardRetentionDocumentCategories enumerates every documents.category bucket +// the per-category retention sweep loops over. MediaCategoryNone covers +// unclassified documents (stickers and anything else classifyDocumentCategory +// doesn't tag) and always uses the shared global age -- there is no +// per-category override for that bucket. Photo/Avatar are not in this list: +// photos have no category column and are instead split by a profile_photos +// join (see categoryRetentionAge/avatarRetentionAge below and the dedicated +// photo/avatar query variants). +var hardRetentionDocumentCategories = []domain.MediaCategory{ + domain.MediaCategoryNone, + domain.MediaCategoryVideo, + domain.MediaCategoryGif, + domain.MediaCategoryFile, + domain.MediaCategoryMusic, + domain.MediaCategoryVoice, + domain.MediaCategoryRoundVideo, +} + +// categoryRetentionAge returns the effective retention age for a document +// media category: its configured override if positive, otherwise the shared +// global age. +func (s *Service) categoryRetentionAge(category domain.MediaCategory) time.Duration { + if age, ok := s.storageRetentionCategoryAges[category]; ok && age > 0 { + return age + } + return s.storageRetentionGlobalMaxAge +} + +// avatarRetentionAge is the Photo category's counterpart for photos currently +// active as someone's avatar -- see categoryRetentionAge. +func (s *Service) avatarRetentionAge() time.Duration { + if s.storageRetentionAvatarMaxAge > 0 { + return s.storageRetentionAvatarMaxAge + } + return s.storageRetentionGlobalMaxAge +} + // mediaRetentionStore is implemented by store.MediaStore backends that // support the storage retention sweep (currently only the Postgres store). // A type assertion, not a MediaStore interface method, keeps these // admin/maintenance-only queries out of the hot RPC-facing interface -- // same convention as photoBatchStore above. type mediaRetentionStore interface { - ListOrphanedDocumentIDsOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) + ListOrphanedDocumentIDsOlderThan(ctx context.Context, category domain.MediaCategory, cutoff time.Time, limit int) ([]int64, error) ListOrphanedPhotoIDsOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) + ListAvatarOrphanedPhotoIDsOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) CountFileBlobRefs(ctx context.Context, backend, objectKey string) (int, error) DeleteDocumentAndBlobs(ctx context.Context, id int64) ([]domain.FileBlob, error) DeletePhotoAndBlobs(ctx context.Context, id int64) ([]domain.FileBlob, error) @@ -31,111 +70,250 @@ type mediaRetentionStore interface { // orphaned one once it's old enough. The delete methods physically // remove only the file_blobs row(s)/bytes, never the document/photo // metadata row -- see DeleteFileBlobsForDocument's doc comment. - ListDocumentIDsForHardRetentionOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) + ListDocumentIDsForHardRetentionOlderThan(ctx context.Context, category domain.MediaCategory, cutoff time.Time, limit int) ([]int64, error) ListPhotoIDsForHardRetentionOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) + ListAvatarPhotoIDsForHardRetentionOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) DeleteFileBlobsForDocument(ctx context.Context, id int64) ([]domain.FileBlob, error) DeleteFileBlobsForPhoto(ctx context.Context, id int64) ([]domain.FileBlob, error) + + // -- active eviction (TELESRV_STORAGE_EVICTION_ENABLE) -- + SumFileBlobBytes(ctx context.Context) (int64, error) + ListOldestMediaForEviction(ctx context.Context, limit int) ([]domain.EvictionCandidate, error) + + // -- retention purge notice (see retention_purge.go) -- + ListMediaReferences(ctx context.Context, kind domain.MediaKind, mediaID int64) ([]domain.MediaReference, error) } // DeleteOrphanedOlderThan implements maintenance.OrphanedMediaRetentionStore: // permanently deletes documents/photos that have had no live reference // (message/profile-photo/sticker-set, see media_references) since at least -// cutoff, along with their blob(s) -- but only physically removes bytes -// from the backend once confirming no other file_blobs row still needs the -// object, since content-addressed storage means the same bytes can be -// shared across documents/photos. -func (s *Service) DeleteOrphanedOlderThan(ctx context.Context, cutoff time.Time, limit int) (int, error) { +// the per-category cutoff derived from now, along with their blob(s) -- but +// only physically removes bytes from the backend once confirming no other +// file_blobs row still needs the object, since content-addressed storage +// means the same bytes can be shared across documents/photos. Loops one +// query per document category (each with its own effective age, see +// categoryRetentionAge) plus a regular/avatar split for photos; limit applies +// per category per tick, not as one shared budget across all of them -- +// simplest correct behavior, revisit only if one category starves another in +// practice. +func (s *Service) DeleteOrphanedOlderThan(ctx context.Context, now time.Time, limit int) (int, error) { store, ok := s.media.(mediaRetentionStore) if !ok || limit <= 0 { return 0, nil } deleted := 0 - docIDs, err := store.ListOrphanedDocumentIDsOlderThan(ctx, cutoff, limit) - if err != nil { - return deleted, fmt.Errorf("list orphaned documents: %w", err) - } - for _, id := range docIDs { - blobs, err := store.DeleteDocumentAndBlobs(ctx, id) - if err != nil { - s.log.Warn("delete orphaned document failed", zap.Int64("document_id", id), zap.Error(err)) + for _, cat := range hardRetentionDocumentCategories { + age := s.categoryRetentionAge(cat) + if age <= 0 { + // Global age is 0 (retention only enabled for other, explicitly + // overridden categories) and this category has no override of its + // own -- skip it entirely rather than treating age<=0 as an + // immediate "everything is older than now" cutoff. continue } - s.deleteOrphanedBlobs(ctx, store, blobs) - deleted++ - } - photoIDs, err := store.ListOrphanedPhotoIDsOlderThan(ctx, cutoff, limit) - if err != nil { - return deleted, fmt.Errorf("list orphaned photos: %w", err) - } - for _, id := range photoIDs { - blobs, err := store.DeletePhotoAndBlobs(ctx, id) + cutoff := now.Add(-age) + docIDs, err := store.ListOrphanedDocumentIDsOlderThan(ctx, cat, cutoff, limit) if err != nil { - s.log.Warn("delete orphaned photo failed", zap.Int64("photo_id", id), zap.Error(err)) - continue + return deleted, fmt.Errorf("list orphaned documents (category %d): %w", cat, err) + } + for _, id := range docIDs { + blobs, err := store.DeleteDocumentAndBlobs(ctx, id) + if err != nil { + s.log.Warn("delete orphaned document failed", zap.Int64("document_id", id), zap.Error(err)) + continue + } + s.deleteOrphanedBlobs(ctx, store, blobs) + deleted++ + } + } + if age := s.categoryRetentionAge(domain.MediaCategoryPhoto); age > 0 { + photoCutoff := now.Add(-age) + photoIDs, err := store.ListOrphanedPhotoIDsOlderThan(ctx, photoCutoff, limit) + if err != nil { + return deleted, fmt.Errorf("list orphaned photos: %w", err) + } + for _, id := range photoIDs { + blobs, err := store.DeletePhotoAndBlobs(ctx, id) + if err != nil { + s.log.Warn("delete orphaned photo failed", zap.Int64("photo_id", id), zap.Error(err)) + continue + } + s.deleteOrphanedBlobs(ctx, store, blobs) + deleted++ + } + } + if age := s.avatarRetentionAge(); age > 0 { + avatarCutoff := now.Add(-age) + avatarIDs, err := store.ListAvatarOrphanedPhotoIDsOlderThan(ctx, avatarCutoff, limit) + if err != nil { + return deleted, fmt.Errorf("list orphaned avatar photos: %w", err) + } + for _, id := range avatarIDs { + blobs, err := store.DeletePhotoAndBlobs(ctx, id) + if err != nil { + s.log.Warn("delete orphaned avatar photo failed", zap.Int64("photo_id", id), zap.Error(err)) + continue + } + s.deleteOrphanedBlobs(ctx, store, blobs) + deleted++ } - s.deleteOrphanedBlobs(ctx, store, blobs) - deleted++ } return deleted, nil } // DeleteBlobBytesForMediaOlderThan implements // maintenance.HardMediaRetentionStore ("hard" retention mode): for -// documents/photos whose upload/created_at is older than cutoff, physically -// deletes their blob bytes (main body + thumbnail/rendition variants) from -// the backend and removes their file_blobs rows -- REGARDLESS of whether a -// live message/profile-photo/sticker-set still references them. It -// deliberately never touches the documents/photos metadata row itself: a -// message must still be able to render "here was a photo/document" -// (dimensions, mime type, filename) after its bytes are gone, rather than -// the message breaking outright. A subsequent upload.getFile for the same -// location key finds no file_blobs row and returns LOCATION_INVALID, which -// stock clients already render as a "media unavailable" placeholder. -func (s *Service) DeleteBlobBytesForMediaOlderThan(ctx context.Context, cutoff time.Time, limit int) (int, error) { +// documents/photos whose upload/created_at is older than the per-category +// cutoff derived from now (see categoryRetentionAge/avatarRetentionAge), +// physically deletes their blob bytes (main body + thumbnail/rendition +// variants) from the backend and removes their file_blobs rows -- +// REGARDLESS of whether a live message/profile-photo/sticker-set still +// references them. It deliberately never touches the documents/photos +// metadata row itself: a message must still be able to render "here was a +// photo/document" (dimensions, mime type, filename) after its bytes are +// gone, rather than the message breaking outright. A subsequent +// upload.getFile for the same location key finds no file_blobs row and +// returns LOCATION_INVALID, which stock clients already render as a "media +// unavailable" placeholder -- and every message that still embeds the purged +// media additionally gets turned into a visible retention-purge notice (see +// notifyRetentionPurge in retention_purge.go). +func (s *Service) DeleteBlobBytesForMediaOlderThan(ctx context.Context, now time.Time, limit int) (int, error) { store, ok := s.media.(mediaRetentionStore) if !ok || limit <= 0 { return 0, nil } purged := 0 - docIDs, err := store.ListDocumentIDsForHardRetentionOlderThan(ctx, cutoff, limit) - if err != nil { - return purged, fmt.Errorf("list documents for hard retention: %w", err) - } - for _, id := range docIDs { - blobs, err := store.DeleteFileBlobsForDocument(ctx, id) + for _, cat := range hardRetentionDocumentCategories { + age := s.categoryRetentionAge(cat) + if age <= 0 { + // See DeleteOrphanedOlderThan's identical guard: age<=0 means this + // category has no override and the global age is 0 (retention + // enabled only for other categories) -- skip, don't treat it as + // "everything is older than now". + continue + } + cutoff := now.Add(-age) + docIDs, err := store.ListDocumentIDsForHardRetentionOlderThan(ctx, cat, cutoff, limit) if err != nil { - s.log.Warn("hard-delete document blob bytes failed", zap.Int64("document_id", id), zap.Error(err)) - continue + return purged, fmt.Errorf("list documents for hard retention (category %d): %w", cat, err) } - if len(blobs) == 0 { - continue + for _, id := range docIDs { + blobs, err := store.DeleteFileBlobsForDocument(ctx, id) + if err != nil { + s.log.Warn("hard-delete document blob bytes failed", zap.Int64("document_id", id), zap.Error(err)) + continue + } + if len(blobs) == 0 { + continue + } + s.deleteOrphanedBlobs(ctx, store, blobs) + s.notifyRetentionPurge(ctx, domain.MediaKindDocument, id) + purged++ } - s.deleteOrphanedBlobs(ctx, store, blobs) - purged++ } - remaining := limit - len(docIDs) - if remaining <= 0 { - return purged, nil - } - photoIDs, err := store.ListPhotoIDsForHardRetentionOlderThan(ctx, cutoff, remaining) - if err != nil { - return purged, fmt.Errorf("list photos for hard retention: %w", err) - } - for _, id := range photoIDs { - blobs, err := store.DeleteFileBlobsForPhoto(ctx, id) + if age := s.categoryRetentionAge(domain.MediaCategoryPhoto); age > 0 { + photoCutoff := now.Add(-age) + photoIDs, err := store.ListPhotoIDsForHardRetentionOlderThan(ctx, photoCutoff, limit) if err != nil { - s.log.Warn("hard-delete photo blob bytes failed", zap.Int64("photo_id", id), zap.Error(err)) - continue + return purged, fmt.Errorf("list photos for hard retention: %w", err) } - if len(blobs) == 0 { - continue + for _, id := range photoIDs { + blobs, err := store.DeleteFileBlobsForPhoto(ctx, id) + if err != nil { + s.log.Warn("hard-delete photo blob bytes failed", zap.Int64("photo_id", id), zap.Error(err)) + continue + } + if len(blobs) == 0 { + continue + } + s.deleteOrphanedBlobs(ctx, store, blobs) + s.notifyRetentionPurge(ctx, domain.MediaKindPhoto, id) + purged++ + } + } + if age := s.avatarRetentionAge(); age > 0 { + avatarCutoff := now.Add(-age) + avatarIDs, err := store.ListAvatarPhotoIDsForHardRetentionOlderThan(ctx, avatarCutoff, limit) + if err != nil { + return purged, fmt.Errorf("list avatar photos for hard retention: %w", err) + } + for _, id := range avatarIDs { + blobs, err := store.DeleteFileBlobsForPhoto(ctx, id) + if err != nil { + s.log.Warn("hard-delete avatar photo blob bytes failed", zap.Int64("photo_id", id), zap.Error(err)) + continue + } + if len(blobs) == 0 { + continue + } + s.deleteOrphanedBlobs(ctx, store, blobs) + s.notifyRetentionPurge(ctx, domain.MediaKindPhoto, id) + purged++ } - s.deleteOrphanedBlobs(ctx, store, blobs) - purged++ } return purged, nil } +// EvictOldestMediaOverBudget implements maintenance.StorageEvictionStore +// (TELESRV_STORAGE_EVICTION_ENABLE): once total physical blob bytes +// (SumFileBlobBytes) exceed TELESRV_STORAGE_MAX_TOTAL_BYTES, purges the +// oldest documents/photos overall -- interleaved by created_at across both +// tables, regardless of category or age -- reusing the exact same blob-purge +// (DeleteFileBlobsForDocument/DeleteFileBlobsForPhoto) and retention-purge +// notice primitive as "hard" mode. Stops once the running total (tracked +// locally from each purge's returned blob sizes, avoiding a re-query per +// item) is back under budget, or limit purges have happened this tick, +// whichever comes first -- bounding how much one tick can reclaim at once. +func (s *Service) EvictOldestMediaOverBudget(ctx context.Context, limit int) (int, error) { + store, ok := s.media.(mediaRetentionStore) + if !ok || limit <= 0 || s.storageMaxTotalBytes <= 0 { + return 0, nil + } + total, err := store.SumFileBlobBytes(ctx) + if err != nil { + return 0, fmt.Errorf("sum file blob bytes: %w", err) + } + if total <= s.storageMaxTotalBytes { + return 0, nil + } + candidates, err := store.ListOldestMediaForEviction(ctx, limit) + if err != nil { + return 0, fmt.Errorf("list oldest media for eviction: %w", err) + } + sort.Slice(candidates, func(i, j int) bool { return candidates[i].CreatedAt.Before(candidates[j].CreatedAt) }) + evicted := 0 + for _, c := range candidates { + if evicted >= limit || total <= s.storageMaxTotalBytes { + break + } + var blobs []domain.FileBlob + var delErr error + switch c.Kind { + case domain.MediaKindDocument: + blobs, delErr = store.DeleteFileBlobsForDocument(ctx, c.MediaID) + case domain.MediaKindPhoto: + blobs, delErr = store.DeleteFileBlobsForPhoto(ctx, c.MediaID) + default: + continue + } + if delErr != nil { + s.log.Warn("active storage eviction blob purge failed", + zap.String("media_kind", string(c.Kind)), zap.Int64("media_id", c.MediaID), zap.Error(delErr)) + continue + } + if len(blobs) == 0 { + continue + } + s.deleteOrphanedBlobs(ctx, store, blobs) + for _, b := range blobs { + total -= b.Size + } + s.notifyRetentionPurge(ctx, c.Kind, c.MediaID) + evicted++ + } + return evicted, nil +} + // deleteDocumentNowIfUnreferenced is the immediate counterpart to the // age-based sweep DeleteOrphanedOlderThan runs in the background: orphans // id right now (skipping the grace period) and, only if that succeeds -- diff --git a/internal/app/files/retention_category_zero_test.go b/internal/app/files/retention_category_zero_test.go new file mode 100644 index 00000000..dcb9ce7a --- /dev/null +++ b/internal/app/files/retention_category_zero_test.go @@ -0,0 +1,153 @@ +package files + +import ( + "context" + "testing" + "time" + + "go.uber.org/zap" + + "telesrv/internal/domain" + "telesrv/internal/store" +) + +// fakeCategorySweepStore records which category/photo/avatar queries +// DeleteBlobBytesForMediaOlderThan/DeleteOrphanedOlderThan actually issue, so +// the zero-age skip guard can be checked without a live database. Embeds a +// nil store.MediaStore so it satisfies that (large) interface for free via +// promoted methods that must never actually be called here -- only the +// mediaRetentionStore subset overridden below is exercised. +type fakeCategorySweepStore struct { + store.MediaStore + queriedDocCategories []domain.MediaCategory + queriedPhotos bool + queriedAvatars bool +} + +func (f *fakeCategorySweepStore) ListDocumentIDsForHardRetentionOlderThan(_ context.Context, category domain.MediaCategory, _ time.Time, _ int) ([]int64, error) { + f.queriedDocCategories = append(f.queriedDocCategories, category) + return nil, nil +} + +func (f *fakeCategorySweepStore) ListPhotoIDsForHardRetentionOlderThan(context.Context, time.Time, int) ([]int64, error) { + f.queriedPhotos = true + return nil, nil +} + +func (f *fakeCategorySweepStore) ListAvatarPhotoIDsForHardRetentionOlderThan(context.Context, time.Time, int) ([]int64, error) { + f.queriedAvatars = true + return nil, nil +} + +func (f *fakeCategorySweepStore) ListOrphanedDocumentIDsOlderThan(_ context.Context, category domain.MediaCategory, _ time.Time, _ int) ([]int64, error) { + f.queriedDocCategories = append(f.queriedDocCategories, category) + return nil, nil +} + +func (f *fakeCategorySweepStore) ListOrphanedPhotoIDsOlderThan(context.Context, time.Time, int) ([]int64, error) { + f.queriedPhotos = true + return nil, nil +} + +func (f *fakeCategorySweepStore) ListAvatarOrphanedPhotoIDsOlderThan(context.Context, time.Time, int) ([]int64, error) { + f.queriedAvatars = true + return nil, nil +} + +// The rest of mediaRetentionStore's methods aren't part of store.MediaStore +// (see that interface's own doc comment -- they're kept out of the hot +// RPC-facing interface on purpose), so embedding store.MediaStore alone +// doesn't provide them. Stub them out; this test never exercises them since +// every List* above returns no candidates. +func (f *fakeCategorySweepStore) CountFileBlobRefs(context.Context, string, string) (int, error) { + return 0, nil +} +func (f *fakeCategorySweepStore) DeleteDocumentAndBlobs(context.Context, int64) ([]domain.FileBlob, error) { + return nil, nil +} +func (f *fakeCategorySweepStore) DeletePhotoAndBlobs(context.Context, int64) ([]domain.FileBlob, error) { + return nil, nil +} +func (f *fakeCategorySweepStore) OrphanDocumentIfUnreferenced(context.Context, int64) (bool, error) { + return false, nil +} +func (f *fakeCategorySweepStore) DeleteFileBlobsForDocument(context.Context, int64) ([]domain.FileBlob, error) { + return nil, nil +} +func (f *fakeCategorySweepStore) DeleteFileBlobsForPhoto(context.Context, int64) ([]domain.FileBlob, error) { + return nil, nil +} +func (f *fakeCategorySweepStore) SumFileBlobBytes(context.Context) (int64, error) { + return 0, nil +} +func (f *fakeCategorySweepStore) ListOldestMediaForEviction(context.Context, int) ([]domain.EvictionCandidate, error) { + return nil, nil +} +func (f *fakeCategorySweepStore) ListMediaReferences(context.Context, domain.MediaKind, int64) ([]domain.MediaReference, error) { + return nil, nil +} + +// TestZeroGlobalRetentionAgeSkipsCategoriesWithoutOverride guards the bug +// reported live: setting the shared Retention age to 0 (meaning "only clean +// up categories I explicitly override") used to be indistinguishable from +// "everything is older than right now" at the per-category cutoff math, +// which would have hard-purged every uncategorized/photo/avatar item +// immediately. The sweep must instead skip any category whose *effective* +// age (its own override, or the 0 global) is <=0 entirely -- no query issued +// for it at all -- while still sweeping a category that has a real, +// positive override. +func TestZeroGlobalRetentionAgeSkipsCategoriesWithoutOverride(t *testing.T) { + fake := &fakeCategorySweepStore{} + s := &Service{ + media: fake, + log: zap.NewNop(), + storageRetentionGlobalMaxAge: 0, + storageRetentionCategoryAges: map[domain.MediaCategory]time.Duration{ + domain.MediaCategoryVideo: 24 * time.Hour, + }, + storageRetentionAvatarMaxAge: 0, + } + + if _, err := s.DeleteBlobBytesForMediaOlderThan(context.Background(), time.Now(), 50); err != nil { + t.Fatalf("DeleteBlobBytesForMediaOlderThan: %v", err) + } + + if len(fake.queriedDocCategories) != 1 || fake.queriedDocCategories[0] != domain.MediaCategoryVideo { + t.Fatalf("queried document categories = %v, want only [Video]", fake.queriedDocCategories) + } + if fake.queriedPhotos { + t.Fatal("queried regular photos with no photo override and global age 0, want skipped") + } + if fake.queriedAvatars { + t.Fatal("queried avatar photos with no avatar override and global age 0, want skipped") + } +} + +// TestZeroGlobalRetentionAgeSkipsOrphanSweepCategoriesWithoutOverride is the +// orphan-mode counterpart of the hard-mode test above. +func TestZeroGlobalRetentionAgeSkipsOrphanSweepCategoriesWithoutOverride(t *testing.T) { + fake := &fakeCategorySweepStore{} + s := &Service{ + media: fake, + log: zap.NewNop(), + storageRetentionGlobalMaxAge: 0, + storageRetentionCategoryAges: map[domain.MediaCategory]time.Duration{ + domain.MediaCategoryVoice: 48 * time.Hour, + }, + storageRetentionAvatarMaxAge: 0, + } + + if _, err := s.DeleteOrphanedOlderThan(context.Background(), time.Now(), 50); err != nil { + t.Fatalf("DeleteOrphanedOlderThan: %v", err) + } + + if len(fake.queriedDocCategories) != 1 || fake.queriedDocCategories[0] != domain.MediaCategoryVoice { + t.Fatalf("queried document categories = %v, want only [Voice]", fake.queriedDocCategories) + } + if fake.queriedPhotos { + t.Fatal("queried regular photos with no photo override and global age 0, want skipped") + } + if fake.queriedAvatars { + t.Fatal("queried avatar photos with no avatar override and global age 0, want skipped") + } +} diff --git a/internal/app/files/retention_purge.go b/internal/app/files/retention_purge.go new file mode 100644 index 00000000..a18cba08 --- /dev/null +++ b/internal/app/files/retention_purge.go @@ -0,0 +1,204 @@ +package files + +import ( + "context" + "errors" + "fmt" + + "go.uber.org/zap" + + "telesrv/internal/domain" +) + +// RetentionPurgeMessageEditor is the private-message capability the storage +// retention sweep needs to turn a hard-retention/eviction blob purge into a +// visible messageActionCustomAction notice. Satisfied by +// internal/app/messages.Service (GetMessages resolves the box's peer -- +// EditMessageRequest requires it, and message_box ref_key only encodes +// owner_user_id+box_id -- then EditMessage performs the actual in-place +// edit with RetentionPurge set). +type RetentionPurgeMessageEditor interface { + GetMessages(ctx context.Context, userID int64, ids []int) (domain.MessageList, error) + EditMessage(ctx context.Context, userID int64, req domain.EditMessageRequest) (domain.EditMessageResult, error) +} + +// RetentionPurgeChannelEditor is the channel-message counterpart of +// RetentionPurgeMessageEditor. Satisfied by internal/app/channels.Service. +// Unlike private messages, EditChannelMessageRequest needs no peer lookup -- +// channelMessageRefKey already encodes the channel id directly. Uses +// EditChannelMessageInternal (not the ordinary EditMessage) since this is a +// server-internal edit with no acting user/channel member behind it. +type RetentionPurgeChannelEditor interface { + EditChannelMessageInternal(ctx context.Context, req domain.EditChannelMessageRequest) (domain.EditChannelMessageResult, error) +} + +// SetRetentionPurgeNotifier wires the private-message/channel-message edit +// capability the storage retention sweep needs to turn a hard-retention/ +// eviction blob purge into a visible messageActionCustomAction notice on +// every message still embedding the purged document/photo. Both app-layer +// services are constructed after this Service in cmd/telesrv/main.go (and +// the background retention sweep goroutine is started before either exists), +// so this is a post-construction setter rather than a NewService option: a +// sweep tick that races ahead of this call simply finds both fields nil and +// skips the notice for that tick, same as any other per-reference failure +// below (best-effort -- the underlying blob purge has already committed and +// must never be undone or retried because of a notice failure). +func (s *Service) SetRetentionPurgeNotifier(messages RetentionPurgeMessageEditor, channels RetentionPurgeChannelEditor) { + s.retentionNotifyMu.Lock() + defer s.retentionNotifyMu.Unlock() + s.retentionMessages = messages + s.retentionChannels = channels +} + +func (s *Service) retentionNotifier() (RetentionPurgeMessageEditor, RetentionPurgeChannelEditor) { + s.retentionNotifyMu.RLock() + defer s.retentionNotifyMu.RUnlock() + return s.retentionMessages, s.retentionChannels +} + +// notifyRetentionPurge turns a just-completed hard-retention/eviction blob +// purge of a document/photo into a visible service-message notice on every +// message that still embeds it. profile_photo/sticker_set/gift references +// have no message to edit and are skipped. Best-effort throughout: a lookup +// or edit failure is logged and never propagated -- the underlying blob purge +// has already committed and must not be undone or retried because of this. +func (s *Service) notifyRetentionPurge(ctx context.Context, kind domain.MediaKind, mediaID int64) { + messages, channels := s.retentionNotifier() + if messages == nil && channels == nil { + return + } + store, ok := s.media.(mediaRetentionStore) + if !ok { + return + } + refs, err := store.ListMediaReferences(ctx, kind, mediaID) + if err != nil { + s.log.Warn("list media references for retention purge notice failed", + zap.String("media_kind", string(kind)), zap.Int64("media_id", mediaID), zap.Error(err)) + return + } + for _, ref := range refs { + switch ref.RefKind { + case domain.MediaRefKindMessageBox: + s.notifyRetentionPurgeMessageBox(ctx, messages, ref.RefKey) + case domain.MediaRefKindChannelMessage: + s.notifyRetentionPurgeChannelMessage(ctx, channels, ref.RefKey) + case domain.MediaRefKindProfilePhoto, domain.MediaRefKindStickerSet, domain.MediaRefKindGift: + // No message to edit for these ref kinds. + } + } +} + +// notifyRetentionPurgeMessageBox parses a message_box ref_key (format +// "user::box:", see postgres.messageBoxRefKey -- kept +// in sync by convention, not a shared symbol, since the ref_key encoding is +// an internal storage detail of that package) and edits the box in place. +func (s *Service) notifyRetentionPurgeMessageBox(ctx context.Context, messages RetentionPurgeMessageEditor, refKey string) { + if messages == nil { + return + } + ownerUserID, boxID, ok := parseMessageBoxRefKey(refKey) + if !ok { + s.log.Warn("unparseable message_box retention purge ref_key", zap.String("ref_key", refKey)) + return + } + // EditMessageRequest requires the peer explicitly (an ordinary edit's RPC + // caller always supplies it) -- resolve it from the box row itself since + // this is a server-internal edit with no client request behind it. + list, err := messages.GetMessages(ctx, ownerUserID, []int{boxID}) + if err != nil { + s.log.Warn("resolve message_box peer for retention purge notice failed", + zap.Int64("owner_user_id", ownerUserID), zap.Int("box_id", boxID), zap.Error(err)) + return + } + if len(list.Messages) == 0 { + // Box no longer visible to its owner (deleted) -- nothing to notice. + return + } + peer := list.Messages[0].Peer + _, err = messages.EditMessage(ctx, ownerUserID, domain.EditMessageRequest{ + OwnerUserID: ownerUserID, + Peer: peer, + ID: boxID, + SetRichMessage: true, + RichMessage: nil, + Media: retentionPurgeNoticeMedia(), + RetentionPurge: true, + }) + if err != nil && !errors.Is(err, domain.ErrMessageNotModified) && !errors.Is(err, domain.ErrMessageIDInvalid) { + s.log.Warn("retention purge notice edit failed (message_box)", + zap.Int64("owner_user_id", ownerUserID), zap.Int("box_id", boxID), zap.Error(err)) + } +} + +// notifyRetentionPurgeChannelMessage parses a channel_message ref_key +// (format "channel::msg:", see +// postgres.channelMessageRefKey) and edits the message in place. Unlike +// private messages, no peer lookup is needed: EditChannelMessageRequest only +// needs the channel id, already encoded in the ref_key. +func (s *Service) notifyRetentionPurgeChannelMessage(ctx context.Context, channels RetentionPurgeChannelEditor, refKey string) { + if channels == nil { + return + } + channelID, messageID, ok := parseChannelMessageRefKey(refKey) + if !ok { + s.log.Warn("unparseable channel_message retention purge ref_key", zap.String("ref_key", refKey)) + return + } + _, err := channels.EditChannelMessageInternal(ctx, domain.EditChannelMessageRequest{ + ChannelID: channelID, + ID: messageID, + RetentionPurge: true, + RetentionPurgeAction: &domain.ChannelMessageAction{Type: domain.ChannelActionCustomText, Text: domain.RetentionPurgeNoticeText}, + }) + if err != nil && !errors.Is(err, domain.ErrMessageNotModified) && !errors.Is(err, domain.ErrMessageIDInvalid) { + s.log.Warn("retention purge notice edit failed (channel_message)", + zap.Int64("channel_id", channelID), zap.Int("message_id", messageID), zap.Error(err)) + } +} + +// retentionPurgeNoticeMedia builds the messageActionCustomAction service +// media payload private-message edits replace a purged message's media with. +func retentionPurgeNoticeMedia() *domain.MessageMedia { + return &domain.MessageMedia{ + Kind: domain.MessageMediaKindService, + ServiceAction: &domain.MessageServiceAction{ + Kind: domain.MessageServiceActionCustomText, + Text: domain.RetentionPurgeNoticeText, + }, + } +} + +// parseMessageBoxRefKey parses "user::box:". +// fmt.Sscanf reports success and silently ignores unconsumed trailing input +// (e.g. "user:1:box:2garbage" would still scan fine), so a bare cnt/err check +// isn't enough -- round-trip the parsed ids back through the same format +// (messageBoxRefKey's own layout) and require an exact match to reject any +// malformed/truncated/trailing-garbage key. Defensive only: ref_key is +// server-written and never externally supplied, but a bug or future +// encoding change should fail closed here, not silently misdirect an edit at +// the wrong box. +func parseMessageBoxRefKey(refKey string) (ownerUserID int64, boxID int, ok bool) { + cnt, err := fmt.Sscanf(refKey, "user:%d:box:%d", &ownerUserID, &boxID) + if err != nil || cnt != 2 || ownerUserID == 0 || boxID == 0 { + return 0, 0, false + } + if fmt.Sprintf("user:%d:box:%d", ownerUserID, boxID) != refKey { + return 0, 0, false + } + return ownerUserID, boxID, true +} + +// parseChannelMessageRefKey parses "channel::msg:". +// See parseMessageBoxRefKey's doc comment -- same round-trip defense against +// trailing-garbage input Sscanf alone would silently accept. +func parseChannelMessageRefKey(refKey string) (channelID int64, messageID int, ok bool) { + cnt, err := fmt.Sscanf(refKey, "channel:%d:msg:%d", &channelID, &messageID) + if err != nil || cnt != 2 || channelID == 0 || messageID == 0 { + return 0, 0, false + } + if fmt.Sprintf("channel:%d:msg:%d", channelID, messageID) != refKey { + return 0, 0, false + } + return channelID, messageID, true +} diff --git a/internal/app/files/retention_purge_test.go b/internal/app/files/retention_purge_test.go new file mode 100644 index 00000000..3f8ea7f7 --- /dev/null +++ b/internal/app/files/retention_purge_test.go @@ -0,0 +1,29 @@ +package files + +import "testing" + +func TestParseMessageBoxRefKeyRoundTrip(t *testing.T) { + owner, box, ok := parseMessageBoxRefKey("user:42:box:7") + if !ok || owner != 42 || box != 7 { + t.Fatalf("parse = (%d, %d, %v), want (42, 7, true)", owner, box, ok) + } + malformed := []string{"", "garbage", "user:1:box:", "user:1:box:2extra", "user:0:box:1", "user:1:box:0", "channel:1:msg:2"} + for _, key := range malformed { + if _, _, ok := parseMessageBoxRefKey(key); ok { + t.Fatalf("parseMessageBoxRefKey(%q) ok=true, want false", key) + } + } +} + +func TestParseChannelMessageRefKeyRoundTrip(t *testing.T) { + channel, msg, ok := parseChannelMessageRefKey("channel:99:msg:3") + if !ok || channel != 99 || msg != 3 { + t.Fatalf("parse = (%d, %d, %v), want (99, 3, true)", channel, msg, ok) + } + malformed := []string{"", "garbage", "channel:1:msg:", "channel:1:msg:2extra", "channel:0:msg:1", "channel:1:msg:0", "user:1:box:2"} + for _, key := range malformed { + if _, _, ok := parseChannelMessageRefKey(key); ok { + t.Fatalf("parseChannelMessageRefKey(%q) ok=true, want false", key) + } + } +} diff --git a/internal/app/files/service.go b/internal/app/files/service.go index 13660017..0ebb7ff3 100644 --- a/internal/app/files/service.go +++ b/internal/app/files/service.go @@ -100,6 +100,27 @@ type Service struct { // restart -- deleting only the DB row would make a "deleted" GIF come // back on its own. gifSeedDir string + + // storageRetentionGlobalMaxAge/storageRetentionCategoryAges/ + // storageRetentionAvatarMaxAge back categoryRetentionAge/avatarRetentionAge + // (retention.go) -- the per-category storage retention age overrides plus + // the shared global fallback age. Set via WithStorageRetentionAges. + storageRetentionGlobalMaxAge time.Duration + storageRetentionCategoryAges map[domain.MediaCategory]time.Duration + storageRetentionAvatarMaxAge time.Duration + // storageMaxTotalBytes is TELESRV_STORAGE_MAX_TOTAL_BYTES, reused by + // EvictOldestMediaOverBudget as the active-eviction trigger threshold + // (<=0 disables eviction regardless of TELESRV_STORAGE_EVICTION_ENABLE). + storageMaxTotalBytes int64 + + // retentionNotifyMu guards retentionMessages/retentionChannels: they are + // set post-construction (see SetRetentionPurgeNotifier) from + // cmd/telesrv/main.go once messageapp/channelapp services exist, which + // happens after this Service and the background retention sweep that + // reads them are already running. + retentionNotifyMu sync.RWMutex + retentionMessages RetentionPurgeMessageEditor + retentionChannels RetentionPurgeChannelEditor } // Option 配置 files 服务的可选能力。 @@ -200,6 +221,27 @@ func WithGifCatalog(c store.GifCatalogStore) Option { } } +// WithStorageRetentionAges configures the per-category storage retention age +// overrides (Config.StorageRetentionMaxAgeByCategory/-MaxAgeAvatar) plus the +// shared global fallback age (Config.StorageRetentionMaxAge), consumed by +// categoryRetentionAge/avatarRetentionAge in retention.go. +func WithStorageRetentionAges(global time.Duration, byCategory map[domain.MediaCategory]time.Duration, avatarMaxAge time.Duration) Option { + return func(s *Service) { + s.storageRetentionGlobalMaxAge = global + s.storageRetentionCategoryAges = byCategory + s.storageRetentionAvatarMaxAge = avatarMaxAge + } +} + +// WithStorageMaxTotalBytes records TELESRV_STORAGE_MAX_TOTAL_BYTES for +// EvictOldestMediaOverBudget's active-eviction trigger threshold, reusing the +// same cap SpaceGuard already enforces against new uploads. +func WithStorageMaxTotalBytes(maxBytes int64) Option { + return func(s *Service) { + s.storageMaxTotalBytes = maxBytes + } +} + // WithGifSeedDir records the gif seed directory (cfg.GifSeedDir) so // AdminDeleteUncategorizedGifs can remove a seed-imported entry's source // file alongside its DB row -- see the field's doc comment for why that diff --git a/internal/app/maintenance/retention.go b/internal/app/maintenance/retention.go index dc627400..01f025d2 100644 --- a/internal/app/maintenance/retention.go +++ b/internal/app/maintenance/retention.go @@ -124,6 +124,8 @@ type RetentionWorker struct { activeAuthKeyHeartbeat ActiveAuthKeyHeartbeatStore orphanedMedia OrphanedMediaRetentionStore hardMedia HardMediaRetentionStore + eviction StorageEvictionStore + evictionEnabled bool logger *zap.Logger retention time.Duration botAPIRetention time.Duration @@ -311,8 +313,19 @@ func (w *RetentionWorker) mediaRetentionInterval() time.Duration { if w.hardMedia != nil && w.hardMediaMaxAge > 0 && (maxAge == 0 || w.hardMediaMaxAge < maxAge) { maxAge = w.hardMediaMaxAge } + evictionActive := w.eviction != nil && w.evictionEnabled if maxAge <= 0 { - return 0 + if !evictionActive { + return 0 + } + // Eviction is reactive to total bytes, not a fixed age, and is + // independent of TELESRV_STORAGE_RETENTION_MODE -- it can be the only + // thing enabled. Fall back to the shared housekeeping interval so it + // still gets its own ticker instead of never running. + maxAge = w.interval + if maxAge <= 0 { + maxAge = time.Hour + } } interval := w.interval if interval <= 0 || maxAge < interval { @@ -324,12 +337,36 @@ func (w *RetentionWorker) mediaRetentionInterval() time.Duration { return interval } +// StorageEvictionStore actively reclaims space once total physical blob bytes +// exceed TELESRV_STORAGE_MAX_TOTAL_BYTES: the oldest documents/photos +// (interleaved by created_at across both tables, regardless of category or +// age) are purged -- reusing the exact same blob-purge + retention-purge +// notice primitive as HardMediaRetentionStore -- until back under budget. +// Independent of TELESRV_STORAGE_RETENTION_MODE. +type StorageEvictionStore interface { + EvictOldestMediaOverBudget(ctx context.Context, limit int) (int, error) +} + +// WithStorageEviction enables the active eviction sweep +// (TELESRV_STORAGE_EVICTION_ENABLE). It shares the same ticker as the +// orphan/hard media sweeps (see mediaRetentionInterval) and is independent of +// TELESRV_STORAGE_RETENTION_MODE -- it can run even when that is "off". +func (w *RetentionWorker) WithStorageEviction(store StorageEvictionStore, enabled bool) *RetentionWorker { + w.eviction = store + w.evictionEnabled = enabled + return w +} + func (w *RetentionWorker) runMediaRetentionOnce(ctx context.Context) { if w.orphanedMedia != nil && w.orphanedMediaMaxAge > 0 { // Only ever touches documents/photos already marked orphaned (no live // message/profile-photo/sticker-set reference remains) -- media still // visible in a conversation is never a candidate, regardless of age. - mediaDeleted, err := w.orphanedMedia.DeleteOrphanedOlderThan(ctx, time.Now().Add(-w.orphanedMediaMaxAge), w.batch) + // The store itself derives each category's effective cutoff from + // "now" (per-category retention age overrides, global age as + // fallback) -- this worker only owns when the sweep runs, not the + // per-category cutoff math. + mediaDeleted, err := w.orphanedMedia.DeleteOrphanedOlderThan(ctx, time.Now(), w.batch) if err != nil { w.logger.Warn("orphaned media storage retention sweep failed", zap.Error(err)) } else if mediaDeleted > 0 { @@ -337,19 +374,28 @@ func (w *RetentionWorker) runMediaRetentionOnce(ctx context.Context) { } } if w.hardMedia != nil && w.hardMediaMaxAge > 0 { - // "Hard" mode: purges blob bytes for documents/photos older than - // hardMediaMaxAge regardless of whether a live reference remains -- - // the store is required to keep the document/photo metadata row - // intact, only removing file_blobs rows/bytes, so a message still - // renders its media placeholder (LOCATION_INVALID on download) - // instead of breaking outright. - hardDeleted, err := w.hardMedia.DeleteBlobBytesForMediaOlderThan(ctx, time.Now().Add(-w.hardMediaMaxAge), w.batch) + // "Hard" mode: purges blob bytes for documents/photos older than the + // effective per-category age (falling back to hardMediaMaxAge) + // regardless of whether a live reference remains -- the store is + // required to keep the document/photo metadata row intact, only + // removing file_blobs rows/bytes, so a message still renders its + // media placeholder (LOCATION_INVALID on download) instead of + // breaking outright. + hardDeleted, err := w.hardMedia.DeleteBlobBytesForMediaOlderThan(ctx, time.Now(), w.batch) if err != nil { w.logger.Warn("hard media storage retention sweep failed", zap.Error(err)) } else if hardDeleted > 0 { w.logger.Info("hard media storage retention sweep complete", zap.Int("deleted", hardDeleted)) } } + if w.eviction != nil && w.evictionEnabled { + evicted, err := w.eviction.EvictOldestMediaOverBudget(ctx, w.batch) + if err != nil { + w.logger.Warn("active storage eviction sweep failed", zap.Error(err)) + } else if evicted > 0 { + w.logger.Info("active storage eviction sweep complete", zap.Int("evicted", evicted)) + } + } } func (w *RetentionWorker) runOutboxPoisonOnce(ctx context.Context) { diff --git a/internal/config/config.go b/internal/config/config.go index 1fdf02fd..ccc986f4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -357,6 +357,32 @@ type Config struct { // RetentionInterval/RetentionBatch cadence alongside every other // retention check (see maintenance.RetentionWorker). StorageRetentionMaxAge time.Duration + // StorageRetentionMaxAgeByCategory overrides StorageRetentionMaxAge for + // one document media category (Photo/Video/RoundVideo/Gif/Music/Voice/ + // File); a category absent from the map inherits the shared global age. + // Populated from TELESRV_STORAGE_RETENTION_MAX_AGE_ env vars, + // one per category -- unset or non-positive means "inherit global" for + // that category (a document that hasn't been classified, e.g. a sticker, + // always uses the global age -- there is no override for that bucket). + // Avatar is not a domain.MediaCategory (a photo becomes/stops being an + // avatar via profile_photos, not a document attribute), so it gets its + // own StorageRetentionMaxAgeAvatar field instead of a map entry. + StorageRetentionMaxAgeByCategory map[domain.MediaCategory]time.Duration + // StorageRetentionMaxAgeAvatar overrides StorageRetentionMaxAge for + // photos currently active as someone's avatar (profile_photos.active). + // <=0 means "inherit global", same convention as + // StorageRetentionMaxAgeByCategory. + StorageRetentionMaxAgeAvatar time.Duration + // StorageEvictionEnable turns on active eviction: once the total physical + // blob bytes exceed StorageMaxTotalBytes, the oldest documents/photos + // (interleaved by created_at across both tables, regardless of category + // or age) are purged the same way "hard" retention mode purges blob + // bytes -- repeated until back under budget. Independent of + // StorageRetentionMode: eviction can run even when retention mode is + // "off". Default false (opt-in), since this changes what + // StorageMaxTotalBytes has meant so far (block new uploads only -> + // block-and-reclaim from existing ones too). + StorageEvictionEnable bool // StickerSeedDir 是 reaction / sticker 资源种子目录(导入到 documents/sticker_sets + blob)。 StickerSeedDir string // StickerSeedMaxSets 限制导入的常规贴纸集数量(避免启动时导入过多包),<=0 表示不限。 @@ -980,6 +1006,9 @@ func Load() (Config, error) { StorageUsageRefreshInterval: envDurationOr("TELESRV_STORAGE_USAGE_REFRESH_INTERVAL", time.Minute), StorageRetentionMode: strings.ToLower(strings.TrimSpace(envOr("TELESRV_STORAGE_RETENTION_MODE", StorageRetentionModeOff))), StorageRetentionMaxAge: envDurationOr("TELESRV_STORAGE_RETENTION_MAX_AGE", 30*24*time.Hour), + StorageRetentionMaxAgeByCategory: storageRetentionMaxAgeByCategoryFromEnv(envDurationOr), + StorageRetentionMaxAgeAvatar: envDurationOr("TELESRV_STORAGE_RETENTION_MAX_AGE_AVATAR", 0), + StorageEvictionEnable: envBoolOr("TELESRV_STORAGE_EVICTION_ENABLE", false), StickerSeedDir: envOr("TELESRV_STICKER_SEED_DIR", "data/sticker-seed"), StickerSeedMaxSets: envIntOr("TELESRV_STICKER_SEED_MAX_SETS", 300), PremiumPromoSeedDir: envOr("TELESRV_PREMIUM_PROMO_SEED_DIR", "data/premium-promo"), @@ -1518,13 +1547,21 @@ func validateStorageConfig(cfg Config) error { if cfg.StorageRetentionMaxAge < 0 { return fmt.Errorf("TELESRV_STORAGE_RETENTION_MAX_AGE must be non-negative") } + if cfg.StorageRetentionMaxAgeAvatar < 0 { + return fmt.Errorf("TELESRV_STORAGE_RETENTION_MAX_AGE_AVATAR must be non-negative") + } switch cfg.StorageRetentionMode { case StorageRetentionModeOff: // no sweep; StorageRetentionMaxAge is ignored. case StorageRetentionModeOrphan, StorageRetentionModeHard: - if cfg.StorageRetentionMaxAge <= 0 { - return fmt.Errorf("TELESRV_STORAGE_RETENTION_MAX_AGE must be positive when TELESRV_STORAGE_RETENTION_MODE is %q", cfg.StorageRetentionMode) - } + // StorageRetentionMaxAge (the shared default) is no longer required to + // be positive here: it can legitimately be 0 -- "no sweep by default" + // -- while individual TELESRV_STORAGE_RETENTION_MAX_AGE_/ + // _AVATAR overrides opt specific categories in. The sweep itself + // (internal/app/files/retention.go) skips any category whose + // *effective* age (its own override, or this shared default) is <=0, + // so a 0 default with no overrides simply means the mode runs and + // purges nothing -- equivalent to "off" in practice, not dangerous. default: return fmt.Errorf("TELESRV_STORAGE_RETENTION_MODE must be \"off\", \"orphan\", or \"hard\", got %q", cfg.StorageRetentionMode) } @@ -2075,6 +2112,33 @@ func (e envSource) envFloatOr(key string, def float64) float64 { return def } +// storageRetentionMaxAgeByCategoryFromEnv builds Config.StorageRetentionMaxAgeByCategory: +// one TELESRV_STORAGE_RETENTION_MAX_AGE_ env var per document +// media category, using envDurationOr(key, 0) so "unset" and "explicitly 0" +// both cleanly mean "inherit the shared global age" -- only a positive value +// gets an entry in the map. +func storageRetentionMaxAgeByCategoryFromEnv(envDurationOr func(key string, def time.Duration) time.Duration) map[domain.MediaCategory]time.Duration { + entries := []struct { + category domain.MediaCategory + envKey string + }{ + {domain.MediaCategoryPhoto, "TELESRV_STORAGE_RETENTION_MAX_AGE_PHOTO"}, + {domain.MediaCategoryVideo, "TELESRV_STORAGE_RETENTION_MAX_AGE_VIDEO"}, + {domain.MediaCategoryRoundVideo, "TELESRV_STORAGE_RETENTION_MAX_AGE_ROUND_VIDEO"}, + {domain.MediaCategoryGif, "TELESRV_STORAGE_RETENTION_MAX_AGE_GIF"}, + {domain.MediaCategoryMusic, "TELESRV_STORAGE_RETENTION_MAX_AGE_MUSIC"}, + {domain.MediaCategoryVoice, "TELESRV_STORAGE_RETENTION_MAX_AGE_VOICE"}, + {domain.MediaCategoryFile, "TELESRV_STORAGE_RETENTION_MAX_AGE_FILE"}, + } + out := make(map[domain.MediaCategory]time.Duration, len(entries)) + for _, e := range entries { + if age := envDurationOr(e.envKey, 0); age > 0 { + out[e.category] = age + } + } + return out +} + // envDurationOr 读取 time.ParseDuration 格式(如 "200ms"、"30s")的时长配置;解析失败回退默认值。 func (e envSource) envDurationOr(key string, def time.Duration) time.Duration { if v := e.envOr(key, ""); v != "" { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index c518a033..33b237dd 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -550,14 +550,31 @@ func TestLoadRejectsInvalidStorageRetentionMode(t *testing.T) { } } -func TestLoadRejectsStorageRetentionModeWithoutMaxAge(t *testing.T) { +// TestLoadAcceptsStorageRetentionModeWithZeroMaxAgeAndCategoryOverride guards +// a real reported bug: TELESRV_STORAGE_RETENTION_MAX_AGE=0 (the shared +// default) used to be rejected outright whenever the mode was orphan/hard, +// which made it impossible to run the sweep for only specific +// TELESRV_STORAGE_RETENTION_MAX_AGE_ overrides while leaving +// everything else alone. A zero shared default is now legitimate: the sweep +// itself (internal/app/files/retention.go) skips any category whose +// *effective* age is <=0, so 0 here just means "no default sweep" rather +// than "purge everything immediately". +func TestLoadAcceptsStorageRetentionModeWithZeroMaxAgeAndCategoryOverride(t *testing.T) { for _, mode := range []string{StorageRetentionModeOrphan, StorageRetentionModeHard} { t.Run(mode, func(t *testing.T) { disableDefaultConfigFile(t) t.Setenv("TELESRV_STORAGE_RETENTION_MODE", mode) t.Setenv("TELESRV_STORAGE_RETENTION_MAX_AGE", "0s") - if _, err := Load(); err == nil { - t.Fatalf("Load accepted TELESRV_STORAGE_RETENTION_MODE=%s with zero max age", mode) + t.Setenv("TELESRV_STORAGE_RETENTION_MAX_AGE_VIDEO", "24h") + cfg, err := Load() + if err != nil { + t.Fatalf("Load rejected TELESRV_STORAGE_RETENTION_MODE=%s with zero shared max age + a category override: %v", mode, err) + } + if cfg.StorageRetentionMaxAge != 0 { + t.Fatalf("StorageRetentionMaxAge = %v, want 0", cfg.StorageRetentionMaxAge) + } + if got := cfg.StorageRetentionMaxAgeByCategory[domain.MediaCategoryVideo]; got != 24*time.Hour { + t.Fatalf("StorageRetentionMaxAgeByCategory[Video] = %v, want 24h", got) } }) } diff --git a/internal/domain/channel.go b/internal/domain/channel.go index d782bf4d..7da4bba3 100644 --- a/internal/domain/channel.go +++ b/internal/domain/channel.go @@ -634,6 +634,10 @@ const ( ChannelActionSuggestedPostApproval ChannelMessageActionType = "suggested_post_approval" ChannelActionSuggestedPostSuccess ChannelMessageActionType = "suggested_post_success" ChannelActionSuggestedPostRefund ChannelMessageActionType = "suggested_post_refund" + // ChannelActionCustomText 映射 messageActionCustomAction(stock TL 类型): + // 当前唯一用途是把 storage retention 硬回收/主动淘汰清理掉的频道消息就地 + // 转成这条通知(见 EditChannelMessageRequest.RetentionPurge)。 + ChannelActionCustomText ChannelMessageActionType = "custom_text" ) // ChannelMessageAction describes a service action without depending on tg.*. @@ -678,6 +682,8 @@ type ChannelMessageAction struct { SuggestedPostScheduleDate int SuggestedPostPrice *SuggestedPostPrice SuggestedPostPayerInitiated bool + // Text 仅 custom_text 服务消息使用(messageActionCustomAction.message)。 + Text string } // ChannelMessage is a single stored message in a channel/supergroup. @@ -1967,6 +1973,15 @@ type EditChannelMessageRequest struct { // 仅当目标当前 media 仍是 ID==ExpectedWebPageID 的 pending 链接预览才替换。 WebPageResolve bool ExpectedWebPageID int64 + // RetentionPurge 置位时为存储 retention 硬回收/主动淘汰的服务端内部编辑: + // 绕过普通的发送者/管理员编辑权限校验,仅由 internal/app/files 的 + // retention purge 通知路径设置。见 EditMessageRequest.RetentionPurge。 + RetentionPurge bool + // RetentionPurgeAction 与 RetentionPurge 搭配使用:频道消息的服务动作 + // 存于独立的 Action 字段(不像私聊消息把服务动作折进 Media.Kind=service), + // 所以 retention 通知在频道侧必须整条消息转成一条新的服务消息(清空 + // body/media,只设置这个 Action),而不是像私聊那样替换 Media。 + RetentionPurgeAction *ChannelMessageAction } // EditChannelMessageResult describes one channel edit update. diff --git a/internal/domain/media.go b/internal/domain/media.go index b610898b..0a348b36 100644 --- a/internal/domain/media.go +++ b/internal/domain/media.go @@ -591,6 +591,11 @@ const ( // 状态切换与关闭请求。会话级保护不能写入普通消息的 NoForwards 字段。 MessageServiceActionNoForwardsToggle MessageServiceActionKind = "no_forwards_toggle" MessageServiceActionNoForwardsRequest MessageServiceActionKind = "no_forwards_request" + // MessageServiceActionCustomText 映射 messageActionCustomAction(stock TL + // 类型,两端客户端已原生支持,无需任何 client 补丁):服务端把纯文本状态 + // 通知(当前唯一用途:storage retention 硬回收把已清理媒体的消息就地转成 + // 这条通知)渲染成居中灰色系统气泡,而不是一次看起来像普通编辑的文本替换。 + MessageServiceActionCustomText MessageServiceActionKind = "custom_text" ) // MessagePhoneCallAction 是 messageActionPhoneCall 的协议中立载荷。 @@ -673,6 +678,8 @@ type MessageServiceAction struct { RequestedPeer *MessageRequestedPeerAction `json:"requested_peer,omitempty"` ChatThemeEmoticon string `json:"chat_theme_emoticon,omitempty"` NoForwards *MessageNoForwardsAction `json:"no_forwards,omitempty"` + // Text 承载 MessageServiceActionCustomText 的固定通知文案(messageActionCustomAction.message)。 + Text string `json:"text,omitempty"` } // MessageMedia 是一条消息媒体载荷的业务表示(落库为消息行上的 JSONB 快照)。 diff --git a/internal/domain/media_category.go b/internal/domain/media_category.go index ff7adefa..50cd5a33 100644 --- a/internal/domain/media_category.go +++ b/internal/domain/media_category.go @@ -159,6 +159,26 @@ func classifyDocumentCategory(doc *Document) (MediaCategory, bool) { } } +// DocumentMediaCategory returns the single document-attribute category +// (Video/Gif/Music/Voice/RoundVideo/File) for a document media payload, used +// to populate the documents.category column alongside the shared media +// index. Unlike ClassifyMediaCategories (which can also add unrelated +// categories such as URL from message entities), this only ever answers "what +// kind of document is this", returning MediaCategoryNone for a non-document +// payload or for sticker/custom-emoji documents (they classify to nothing -- +// see classifyDocumentCategory). Zero new classification logic: reuses +// classifyDocumentCategory exactly. +func DocumentMediaCategory(media *MessageMedia) MediaCategory { + if media == nil || media.Kind != MessageMediaKindDocument { + return MediaCategoryNone + } + c, ok := classifyDocumentCategory(media.Document) + if !ok { + return MediaCategoryNone + } + return c +} + func hasURLEntity(entities []MessageEntity) bool { for _, e := range entities { if e.Type == MessageEntityURL || e.Type == MessageEntityTextURL || e.Type == MessageEntityEmail { diff --git a/internal/domain/media_refs.go b/internal/domain/media_refs.go index 08c30e65..eff50f30 100644 --- a/internal/domain/media_refs.go +++ b/internal/domain/media_refs.go @@ -1,5 +1,7 @@ package domain +import "time" + // MediaKind identifies which table a media_references row points into. type MediaKind string @@ -45,6 +47,16 @@ type OrphanCandidate struct { OrphanedAt int64 // unix seconds } +// EvictionCandidate is one document/photo still owning file_blobs bytes, +// used by the active-eviction sweep (TELESRV_STORAGE_EVICTION_ENABLE) to pick +// the oldest media overall across both tables once total physical storage +// exceeds TELESRV_STORAGE_MAX_TOTAL_BYTES. +type EvictionCandidate struct { + Kind MediaKind + MediaID int64 + CreatedAt time.Time +} + // MediaRefTarget identifies one document/photo embedded in a message's media // snapshot. type MediaRefTarget struct { diff --git a/internal/domain/message.go b/internal/domain/message.go index f48cf3ab..c52df48c 100644 --- a/internal/domain/message.go +++ b/internal/domain/message.go @@ -555,6 +555,12 @@ type OutboxReadDateRequest struct { ID int } +// RetentionPurgeNoticeText is the fixed notice text used for the +// messageActionCustomAction (MessageServiceActionCustomText) service message +// a storage-retention blob purge (hard mode or active eviction) turns a +// message into once its underlying document/photo bytes are gone. +const RetentionPurgeNoticeText = "This file was deleted by the server's storage retention policy." + // EditMessageRequest 是账号视角下编辑一条已发送私聊消息的命令。 // Media 非 nil 时整体替换消息媒体快照(当前唯一调用方是 live location 续报/停止, // rpc 层负责限定媒体种类);nil 表示纯文本编辑。 @@ -589,6 +595,11 @@ type EditMessageRequest struct { // 否则返回 ErrMessageNotModified(消息已删/已改/已解析)。 WebPageResolve bool ExpectedWebPageID int64 + // RetentionPurge 置位时为存储 retention 硬回收/主动淘汰的服务端内部编辑: + // 绕过 authorEdit/viaBotEdit/WebPageResolve 之外的普通作者校验(回收发生 + // 在任意账号的历史消息上,不是消息真正作者的操作),仅由 + // internal/app/files 的 retention purge 通知路径设置。 + RetentionPurge bool } // EditedMessageForUser 描述一次编辑对某个 owner 视角造成的影响。 diff --git a/internal/rpc/convert_channels_core.go b/internal/rpc/convert_channels_core.go index 85786602..8ff88662 100644 --- a/internal/rpc/convert_channels_core.go +++ b/internal/rpc/convert_channels_core.go @@ -224,6 +224,8 @@ func tgChannelMessageAction(action domain.ChannelMessageAction) tg.MessageAction return &tg.MessageActionChannelCreate{Title: action.Title} case domain.ChannelActionHistoryClear: return &tg.MessageActionHistoryClear{} + case domain.ChannelActionCustomText: + return &tg.MessageActionCustomAction{Message: action.Text} case domain.ChannelActionChatAddUser, domain.ChannelActionChatJoined: return &tg.MessageActionChatAddUser{Users: append([]int64(nil), action.UserIDs...)} case domain.ChannelActionChatJoinedByLink: diff --git a/internal/rpc/convert_messages.go b/internal/rpc/convert_messages.go index 5cf8631f..122253b6 100644 --- a/internal/rpc/convert_messages.go +++ b/internal/rpc/convert_messages.go @@ -139,6 +139,8 @@ func tgMessageServiceAction(msg domain.Message) tg.MessageActionClass { switch m.ServiceAction.Kind { case domain.MessageServiceActionHistoryClear: return &tg.MessageActionHistoryClear{} + case domain.MessageServiceActionCustomText: + return &tg.MessageActionCustomAction{Message: m.ServiceAction.Text} case domain.MessageServiceActionSuggestProfilePhoto: if m.ServiceAction.Photo == nil || m.ServiceAction.Photo.ID == 0 { return &tg.MessageActionEmpty{} diff --git a/internal/store/postgres/channel_message_edit.go b/internal/store/postgres/channel_message_edit.go index ef1a6384..07a2b708 100644 --- a/internal/store/postgres/channel_message_edit.go +++ b/internal/store/postgres/channel_message_edit.go @@ -12,7 +12,12 @@ import ( ) func (s *ChannelStore) EditChannelMessage(ctx context.Context, req domain.EditChannelMessageRequest) (domain.EditChannelMessageResult, error) { - if req.UserID == 0 || req.ChannelID == 0 || req.ID <= 0 { + retentionPurge := req.RetentionPurge && req.RetentionPurgeAction != nil + // RetentionPurge is a server-internal edit with no acting user (the + // storage retention sweep, not an RPC caller) -- UserID==0 is normally + // invalid, and req.UserID would otherwise also need to name an existing + // channel member for getChannelForMember below. + if (req.UserID == 0 && !retentionPurge) || req.ChannelID == 0 || req.ID <= 0 { return domain.EditChannelMessageResult{}, domain.ErrChannelInvalid } beginner, ok := s.db.(txBeginner) @@ -44,7 +49,17 @@ func (s *ChannelStore) EditChannelMessage(ctx context.Context, req domain.EditCh _ = tx.Rollback(ctx) } }() - channel, member, err := s.getChannelForMember(ctx, tx, req.UserID, req.ChannelID) + var ( + channel domain.Channel + member domain.ChannelMember + ) + if retentionPurge { + // No membership requirement: this is a server-internal edit, not an + // action taken by any particular channel member. + channel, err = s.channelByID(ctx, tx, req.ChannelID) + } else { + channel, member, err = s.getChannelForMember(ctx, tx, req.UserID, req.ChannelID) + } if err != nil { return domain.EditChannelMessageResult{}, err } @@ -52,6 +67,17 @@ func (s *ChannelStore) EditChannelMessage(ctx context.Context, req domain.EditCh if err != nil { return domain.EditChannelMessageResult{}, err } + if retentionPurge { + if msg.Deleted { + return domain.EditChannelMessageResult{}, domain.ErrMessageIDInvalid + } + if msg.Action != nil { + // 幂等守卫:已经是服务消息(含已被本路径转换过的 retention 通知) + // 不再重复编辑 -- 见 EditMessageRequest.RetentionPurge 同款守卫。 + return domain.EditChannelMessageResult{}, domain.ErrMessageNotModified + } + return s.applyRetentionPurgeChannelMessage(ctx, tx, channel, msg, req) + } if msg.Deleted || msg.Action != nil { return domain.EditChannelMessageResult{}, domain.ErrMessageIDInvalid } @@ -283,6 +309,74 @@ WHERE channel_id = $1 AND id = $2`, req.ChannelID, req.ID, mediaJSON); err != ni return domain.EditChannelMessageResult{Channel: channel, Message: msg, Event: event, ServiceMessage: serviceMsg, ServiceEvent: serviceEvent, Recipients: recipients}, nil } +// applyRetentionPurgeChannelMessage turns msg into a real service message +// (messageActionCustomAction via req.RetentionPurgeAction) in place. Unlike a +// normal channel edit -- which only ever replaces body/entities/media -- +// channel service actions live in a structurally separate Action column +// (see tgChannelMessage: m.Action != nil renders tg.MessageService instead of +// tg.Message), so this clears body/media and sets Action instead of touching +// them. Reuses the same pts/durable-event/admin-log machinery as a normal +// edit so getChannelDifference and the fanout dispatcher see it identically. +func (s *ChannelStore) applyRetentionPurgeChannelMessage(ctx context.Context, tx pgx.Tx, channel domain.Channel, msg domain.ChannelMessage, req domain.EditChannelMessageRequest) (domain.EditChannelMessageResult, error) { + actionJSON, err := marshalJSON(req.RetentionPurgeAction, "{}") + if err != nil { + return domain.EditChannelMessageResult{}, fmt.Errorf("encode retention purge channel action: %w", err) + } + pts, err := s.reserveChannelPts(ctx, tx, req.ChannelID) + if err != nil { + return domain.EditChannelMessageResult{}, fmt.Errorf("allocate retention purge channel pts: %w", err) + } + if _, err := tx.Exec(ctx, ` +UPDATE channel_messages +SET body = '', entities = '[]'::jsonb, media = '{}'::jsonb, action = $3, edit_date = $4, pts = $5, updated_at = now() +WHERE channel_id = $1 AND id = $2`, req.ChannelID, req.ID, actionJSON, req.EditDate, pts); err != nil { + return domain.EditChannelMessageResult{}, fmt.Errorf("update retention purge channel message: %w", err) + } + prevMsg := msg + msg.Body = "" + msg.Entities = nil + msg.Media = nil + msg.Action = req.RetentionPurgeAction + msg.EditDate = req.EditDate + msg.Pts = pts + // 媒体索引/media_references 靠这次替换后的(空)媒体重建:原文档/照片 + // 不再被这条消息引用,storage retention 的孤儿判定据此推进。 + if err := replaceChannelMediaIndexTx(ctx, tx, req.ChannelID, req.ID, msg.Date, msg.Media, nil); err != nil { + return domain.EditChannelMessageResult{}, err + } + event := domain.ChannelUpdateEvent{ + ChannelID: req.ChannelID, + Type: domain.ChannelUpdateEditMessage, + Pts: pts, + PtsCount: 1, + Date: req.EditDate, + Message: msg, + SenderUserID: req.UserID, + } + if err := insertChannelEventTx(ctx, tx, event); err != nil { + return domain.EditChannelMessageResult{}, err + } + if _, err := tx.Exec(ctx, `UPDATE channels SET pts = $2, updated_at = now() WHERE id = $1`, req.ChannelID, pts); err != nil { + return domain.EditChannelMessageResult{}, fmt.Errorf("update retention purge channel pts: %w", err) + } + channel.Pts = pts + if err := s.insertChannelAdminLogTx(ctx, tx, domain.ChannelAdminLogEvent{ + ChannelID: req.ChannelID, + UserID: req.UserID, + Date: req.EditDate, + Type: domain.ChannelAdminLogEditMessage, + PrevMessage: &prevMsg, + NewMessage: &msg, + }); err != nil { + return domain.EditChannelMessageResult{}, err + } + if err := tx.Commit(ctx); err != nil { + return domain.EditChannelMessageResult{}, fmt.Errorf("commit retention purge channel message: %w", err) + } + recipients, _ := s.ListActiveChannelMemberIDs(ctx, req.UserID, req.ChannelID, 0) + return domain.EditChannelMessageResult{Channel: channel, Message: msg, Event: event, Recipients: recipients}, nil +} + func isChannelTodoParticipantEdit(req domain.EditChannelMessageRequest, msg domain.ChannelMessage) bool { if !req.AllowTodoParticipantMutation || req.SetReplyMarkup || req.Media == nil || req.Media.Kind != domain.MessageMediaKindTodo || req.Media.Todo == nil { return false diff --git a/internal/store/postgres/media_hard_retention_integration_test.go b/internal/store/postgres/media_hard_retention_integration_test.go index cd71b5a4..80d663f9 100644 --- a/internal/store/postgres/media_hard_retention_integration_test.go +++ b/internal/store/postgres/media_hard_retention_integration_test.go @@ -64,7 +64,7 @@ func TestHardRetentionPurgesBlobBytesButKeepsMetadataRow(t *testing.T) { }) cutoff := time.Now().Add(-24 * time.Hour) - ids, err := media.ListDocumentIDsForHardRetentionOlderThan(ctx, cutoff, 1000) + ids, err := media.ListDocumentIDsForHardRetentionOlderThan(ctx, domain.MediaCategoryNone, cutoff, 1000) if err != nil { t.Fatalf("ListDocumentIDsForHardRetentionOlderThan: %v", err) } @@ -95,7 +95,7 @@ func TestHardRetentionPurgesBlobBytesButKeepsMetadataRow(t *testing.T) { // Idempotent / self-terminating: a second sweep pass no longer selects // this document, since it no longer owns any file_blobs row. - ids, err = media.ListDocumentIDsForHardRetentionOlderThan(ctx, cutoff, 1000) + ids, err = media.ListDocumentIDsForHardRetentionOlderThan(ctx, domain.MediaCategoryNone, cutoff, 1000) if err != nil { t.Fatalf("ListDocumentIDsForHardRetentionOlderThan (2nd pass): %v", err) } diff --git a/internal/store/postgres/media_index.go b/internal/store/postgres/media_index.go index a7253b2d..7e261c16 100644 --- a/internal/store/postgres/media_index.go +++ b/internal/store/postgres/media_index.go @@ -24,9 +24,30 @@ ON CONFLICT (channel_id, id, category) DO NOTHING`, channelID, id, int16(c), dat return fmt.Errorf("insert channel media index: %w", err) } } + if err := setDocumentCategoryTx(ctx, tx, media); err != nil { + return err + } return addMediaReferencesTx(ctx, tx, media, domain.MediaRefKindChannelMessage, channelMessageRefKey(channelID, id)) } +// setDocumentCategoryTx persists media.Document's retention-sweep category +// (see domain.DocumentMediaCategory) onto its documents row, alongside the +// message_box_media/channel_message_media index write these callers already +// do -- zero new classification logic, just also stamping the already +// computed value directly onto documents.category so the per-category +// storage retention sweep (internal/app/files/retention.go) can filter on it +// without a join. No-op for non-document media. +func setDocumentCategoryTx(ctx context.Context, tx pgx.Tx, media *domain.MessageMedia) error { + if media == nil || media.Kind != domain.MessageMediaKindDocument || media.Document == nil || media.Document.ID == 0 { + return nil + } + category := domain.DocumentMediaCategory(media) + if _, err := tx.Exec(ctx, `UPDATE documents SET category = $2 WHERE id = $1`, media.Document.ID, int16(category)); err != nil { + return fmt.Errorf("set document category: %w", err) + } + return nil +} + // deleteChannelMediaIndexTx 清掉一条频道消息的全部索引行(编辑改媒体前先清后插)。 // 只在 replaceChannelMediaIndexTx 内被调用;真正的消息删除不清 *_media 分类索引 // (读时靠 JOIN deleted 过滤,见文件头注释),但仍需在此清掉 media_references, @@ -57,6 +78,9 @@ ON CONFLICT (owner_user_id, box_id, category) DO NOTHING`, ownerUserID, boxID, p return fmt.Errorf("insert message box media index: %w", err) } } + if err := setDocumentCategoryTx(ctx, tx, media); err != nil { + return err + } return addMediaReferencesTx(ctx, tx, media, domain.MediaRefKindMessageBox, messageBoxRefKey(ownerUserID, boxID)) } diff --git a/internal/store/postgres/media_refs.go b/internal/store/postgres/media_refs.go index d460bfc9..7db334e7 100644 --- a/internal/store/postgres/media_refs.go +++ b/internal/store/postgres/media_refs.go @@ -105,6 +105,30 @@ RETURNING media_kind, media_id`, string(refKind), refKey) return nil } +// ListMediaReferences returns every live reference row for (kind, mediaID) -- +// used by the storage retention sweep to turn a hard-retention/eviction blob +// purge into a visible notice on every message that still embeds the purged +// media. See internal/app/files.notifyRetentionPurge. +func (s *MediaStore) ListMediaReferences(ctx context.Context, kind domain.MediaKind, mediaID int64) ([]domain.MediaReference, error) { + rows, err := s.q.ListMediaReferences(ctx, sqlcgen.ListMediaReferencesParams{ + MediaKind: string(kind), + MediaID: mediaID, + }) + if err != nil { + return nil, fmt.Errorf("list media references: %w", err) + } + out := make([]domain.MediaReference, 0, len(rows)) + for _, r := range rows { + out = append(out, domain.MediaReference{ + Kind: domain.MediaKind(r.MediaKind), + MediaID: r.MediaID, + RefKind: domain.MediaRefKind(r.RefKind), + RefKey: r.RefKey, + }) + } + return out, nil +} + // ---- storage retention sweep ---- // OrphanDocumentIfUnreferenced marks a document orphaned right now if @@ -128,20 +152,22 @@ WHERE id = $1 return tag.RowsAffected() > 0, nil } -// ListOrphanedDocumentIDsOlderThan returns document ids whose orphaned_at is -// set and older than cutoff, oldest first, up to limit. -func (s *MediaStore) ListOrphanedDocumentIDsOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) { +// ListOrphanedDocumentIDsOlderThan returns document ids in the given category +// whose orphaned_at is set and older than cutoff, oldest first, up to limit. +func (s *MediaStore) ListOrphanedDocumentIDsOlderThan(ctx context.Context, category domain.MediaCategory, cutoff time.Time, limit int) ([]int64, error) { if limit <= 0 { return nil, nil } return s.q.ListOrphanedDocumentIDsOlderThan(ctx, sqlcgen.ListOrphanedDocumentIDsOlderThanParams{ Cutoff: pgtype.Timestamptz{Time: cutoff, Valid: true}, + Category: int16(category), BatchLimit: int32(limit), }) } -// ListOrphanedPhotoIDsOlderThan returns photo ids whose orphaned_at is set -// and older than cutoff, oldest first, up to limit. +// ListOrphanedPhotoIDsOlderThan returns photo ids (excluding a live avatar -- +// see ListAvatarOrphanedPhotoIDsOlderThan) whose orphaned_at is set and older +// than cutoff, oldest first, up to limit. func (s *MediaStore) ListOrphanedPhotoIDsOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) { if limit <= 0 { return nil, nil @@ -152,6 +178,19 @@ func (s *MediaStore) ListOrphanedPhotoIDsOlderThan(ctx context.Context, cutoff t }) } +// ListAvatarOrphanedPhotoIDsOlderThan is the "Avatar" category counterpart of +// ListOrphanedPhotoIDsOlderThan -- see the query's doc comment for why this +// is expected to stay empty in practice under "orphan" mode. +func (s *MediaStore) ListAvatarOrphanedPhotoIDsOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) { + if limit <= 0 { + return nil, nil + } + return s.q.ListAvatarOrphanedPhotoIDsOlderThan(ctx, sqlcgen.ListAvatarOrphanedPhotoIDsOlderThanParams{ + Cutoff: pgtype.Timestamptz{Time: cutoff, Valid: true}, + BatchLimit: int32(limit), + }) +} + // CountFileBlobRefs reports how many file_blobs rows still point at // (backend, objectKey) -- the caller must not physically delete the object // from that backend while this is > 0 (content-addressed storage: the same @@ -197,24 +236,26 @@ func (s *MediaStore) DeleteDocumentAndBlobs(ctx context.Context, id int64) ([]do return blobs, nil } -// ListDocumentIDsForHardRetentionOlderThan returns document ids older than -// cutoff (by upload/created_at) that still own at least one file_blobs row, -// oldest first, up to limit -- the "hard" retention sweep's candidate list. -// Unlike ListOrphanedDocumentIDsOlderThan, this ignores media_references -// entirely: a document still referenced by a live message is exactly as -// eligible as an orphaned one. -func (s *MediaStore) ListDocumentIDsForHardRetentionOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) { +// ListDocumentIDsForHardRetentionOlderThan returns document ids in the given +// category older than cutoff (by upload/created_at) that still own at least +// one file_blobs row, oldest first, up to limit -- the "hard" retention +// sweep's candidate list. Unlike ListOrphanedDocumentIDsOlderThan, this +// ignores media_references entirely: a document still referenced by a live +// message is exactly as eligible as an orphaned one. +func (s *MediaStore) ListDocumentIDsForHardRetentionOlderThan(ctx context.Context, category domain.MediaCategory, cutoff time.Time, limit int) ([]int64, error) { if limit <= 0 { return nil, nil } return s.q.ListDocumentIDsForHardRetentionOlderThan(ctx, sqlcgen.ListDocumentIDsForHardRetentionOlderThanParams{ Cutoff: pgtype.Timestamptz{Time: cutoff, Valid: true}, + Category: int16(category), BatchLimit: int32(limit), }) } // ListPhotoIDsForHardRetentionOlderThan is the photo counterpart of -// ListDocumentIDsForHardRetentionOlderThan. +// ListDocumentIDsForHardRetentionOlderThan (excluding a live avatar -- see +// ListAvatarPhotoIDsForHardRetentionOlderThan). func (s *MediaStore) ListPhotoIDsForHardRetentionOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) { if limit <= 0 { return nil, nil @@ -225,6 +266,18 @@ func (s *MediaStore) ListPhotoIDsForHardRetentionOlderThan(ctx context.Context, }) } +// ListAvatarPhotoIDsForHardRetentionOlderThan is the "Avatar" category +// counterpart of ListPhotoIDsForHardRetentionOlderThan. +func (s *MediaStore) ListAvatarPhotoIDsForHardRetentionOlderThan(ctx context.Context, cutoff time.Time, limit int) ([]int64, error) { + if limit <= 0 { + return nil, nil + } + return s.q.ListAvatarPhotoIDsForHardRetentionOlderThan(ctx, sqlcgen.ListAvatarPhotoIDsForHardRetentionOlderThanParams{ + Cutoff: pgtype.Timestamptz{Time: cutoff, Valid: true}, + BatchLimit: int32(limit), + }) +} + // DeleteFileBlobsForDocument deletes every file_blobs row a document owns // (main body + thumbnail variants), returning what was deleted so the caller // can physically remove each object from its backend once confirming (via @@ -292,6 +345,32 @@ func (s *MediaStore) DeleteFileBlobsForPhoto(ctx context.Context, id int64) ([]d return blobs, nil } +// ListOldestMediaForEviction returns up to limit documents and limit photos +// still owning file_blobs bytes, oldest-uploaded first, for the active +// eviction sweep to interleave by actual created_at (not drain one table +// before the other) -- see domain.EvictionCandidate. +func (s *MediaStore) ListOldestMediaForEviction(ctx context.Context, limit int) ([]domain.EvictionCandidate, error) { + if limit <= 0 { + return nil, nil + } + docs, err := s.q.ListOldestDocumentsForEviction(ctx, int32(limit)) + if err != nil { + return nil, fmt.Errorf("list oldest documents for eviction: %w", err) + } + photos, err := s.q.ListOldestPhotosForEviction(ctx, int32(limit)) + if err != nil { + return nil, fmt.Errorf("list oldest photos for eviction: %w", err) + } + out := make([]domain.EvictionCandidate, 0, len(docs)+len(photos)) + for _, d := range docs { + out = append(out, domain.EvictionCandidate{Kind: domain.MediaKindDocument, MediaID: d.ID, CreatedAt: d.CreatedAt.Time}) + } + for _, p := range photos { + out = append(out, domain.EvictionCandidate{Kind: domain.MediaKindPhoto, MediaID: p.ID, CreatedAt: p.CreatedAt.Time}) + } + return out, nil +} + // DeletePhotoAndBlobs deletes a photo row and every file_blobs row it owns // (one per rendition size), returning what was deleted so the caller can // physically remove each object from its backend once confirming (via diff --git a/internal/store/postgres/message_edit.go b/internal/store/postgres/message_edit.go index 4c4ec81c..ece6640c 100644 --- a/internal/store/postgres/message_edit.go +++ b/internal/store/postgres/message_edit.go @@ -83,9 +83,18 @@ func (s *MessageStore) EditMessage(ctx context.Context, req domain.EditMessageRe } authorEdit := target.Outgoing && target.MessageSenderID == req.OwnerUserID && target.FromUserID == req.OwnerUserID viaBotEdit := req.ViaBotEditBotID != 0 && target.ViaBotID == req.ViaBotEditBotID - if !authorEdit && !viaBotEdit && !req.WebPageResolve && !validTodoParticipantEdit(req, target, oldEntities) { + if !authorEdit && !viaBotEdit && !req.WebPageResolve && !req.RetentionPurge && !validTodoParticipantEdit(req, target, oldEntities) { return res, domain.ErrMessageAuthorRequired } + if req.RetentionPurge { + // 幂等守卫:已经是这条 retention 通知的消息不再重复编辑(同一被回收 + // 媒体可能被多个 box 的 media_references 行各引用一次,比如自己与对端 + // 各自的 box——同一条共享 private_message 只需真正编辑一次)。 + if targetMedia, err := decodeMessageMedia(target.MediaJson); err == nil && + targetMedia != nil && targetMedia.Kind == domain.MessageMediaKindService { + return res, domain.ErrMessageNotModified + } + } richChanged := req.SetRichMessage && !richMessagesEqual(targetRich, req.RichMessage) if req.Media == nil && !req.SetReplyMarkup && !richChanged && target.Body == req.Message && target.HideEdited == req.HideEdited && sameMessageEntities(oldEntities, req.Entities) { return res, domain.ErrMessageNotModified diff --git a/internal/store/postgres/queries/media.sql b/internal/store/postgres/queries/media.sql index fc603aa2..f7a102ac 100644 --- a/internal/store/postgres/queries/media.sql +++ b/internal/store/postgres/queries/media.sql @@ -202,6 +202,16 @@ WHERE id = sqlc.arg(media_id)::bigint AND orphaned_at IS NOT NULL; UPDATE photos SET orphaned_at = NULL WHERE id = sqlc.arg(media_id)::bigint AND orphaned_at IS NOT NULL; +-- name: ListMediaReferences :many +-- Every live reference to a document/photo -- used by the storage retention +-- sweep to turn a hard-retention/eviction blob purge into a visible notice on +-- every message that still embeds the purged media (see +-- files.notifyRetentionPurge). profile_photo/sticker_set/gift refs have no +-- message to edit and are filtered by the caller, not this query. +SELECT media_kind, media_id, ref_kind, ref_key +FROM media_references +WHERE media_kind = sqlc.arg(media_kind)::text AND media_id = sqlc.arg(media_id)::bigint; + -- name: RemoveMediaReference :exec DELETE FROM media_references WHERE media_kind = sqlc.arg(media_kind)::text @@ -226,15 +236,36 @@ WHERE id = sqlc.arg(media_id)::bigint ); -- name: ListOrphanedDocumentIDsOlderThan :many +-- category selects one documents.category bucket per sweep tick (per-category +-- retention age, see internal/app/files/retention.go) -- 0 (MediaCategoryNone) +-- covers unclassified documents (e.g. stickers), which always use the shared +-- global age since there is no per-category override for that bucket. SELECT id FROM documents WHERE orphaned_at IS NOT NULL AND orphaned_at < sqlc.arg(cutoff)::timestamptz + AND category = sqlc.arg(category)::smallint ORDER BY orphaned_at ASC LIMIT sqlc.arg(batch_limit)::int; -- name: ListOrphanedPhotoIDsOlderThan :many -SELECT id FROM photos -WHERE orphaned_at IS NOT NULL AND orphaned_at < sqlc.arg(cutoff)::timestamptz -ORDER BY orphaned_at ASC +-- Excludes photos currently active as someone's avatar -- see +-- ListAvatarOrphanedPhotoIDsOlderThan for that split-off bucket. +SELECT p.id FROM photos p +WHERE p.orphaned_at IS NOT NULL AND p.orphaned_at < sqlc.arg(cutoff)::timestamptz + AND NOT EXISTS (SELECT 1 FROM profile_photos pp WHERE pp.photo_id = p.id AND pp.active) +ORDER BY p.orphaned_at ASC +LIMIT sqlc.arg(batch_limit)::int; + +-- name: ListAvatarOrphanedPhotoIDsOlderThan :many +-- Same as ListOrphanedPhotoIDsOlderThan but only photos currently active as +-- someone's avatar (profile_photos.active) -- lets the Avatar category carry +-- its own retention age. In practice a live avatar is never orphaned (an +-- active profile_photos row is itself a media_references entry), so this +-- bucket is expected to stay empty under "orphan" mode; kept for symmetry +-- with the "hard" mode avatar split, which is the one that actually matters. +SELECT p.id FROM photos p +WHERE p.orphaned_at IS NOT NULL AND p.orphaned_at < sqlc.arg(cutoff)::timestamptz + AND EXISTS (SELECT 1 FROM profile_photos pp WHERE pp.photo_id = p.id AND pp.active) +ORDER BY p.orphaned_at ASC LIMIT sqlc.arg(batch_limit)::int; -- name: ListDocumentIDsForHardRetentionOlderThan :many @@ -244,9 +275,12 @@ LIMIT sqlc.arg(batch_limit)::int; -- keeps this sweep from re-selecting the same document forever: once its -- blob bytes are purged (DeleteFileBlobsForDocument removes the file_blobs -- rows but deliberately leaves this documents row in place), it naturally --- drops out of this query on the next pass. +-- drops out of this query on the next pass. category scopes to one +-- documents.category bucket per sweep tick -- see +-- ListOrphanedDocumentIDsOlderThan for the 0/None fallback note. SELECT d.id FROM documents d WHERE d.created_at < sqlc.arg(cutoff)::timestamptz + AND d.category = sqlc.arg(category)::smallint AND EXISTS ( SELECT 1 FROM file_blobs fb WHERE fb.location_key = 'doc:' || d.id::text @@ -257,9 +291,11 @@ LIMIT sqlc.arg(batch_limit)::int; -- name: ListPhotoIDsForHardRetentionOlderThan :many -- See ListDocumentIDsForHardRetentionOlderThan -- same "hard" retention --- candidate selection, for photos. +-- candidate selection, for photos. Excludes photos currently active as +-- someone's avatar -- see ListAvatarPhotoIDsForHardRetentionOlderThan. SELECT p.id FROM photos p WHERE p.created_at < sqlc.arg(cutoff)::timestamptz + AND NOT EXISTS (SELECT 1 FROM profile_photos pp WHERE pp.photo_id = p.id AND pp.active) AND EXISTS ( SELECT 1 FROM file_blobs fb WHERE fb.location_key = 'photo:' || p.id::text @@ -268,6 +304,52 @@ WHERE p.created_at < sqlc.arg(cutoff)::timestamptz ORDER BY p.created_at ASC LIMIT sqlc.arg(batch_limit)::int; +-- name: ListAvatarPhotoIDsForHardRetentionOlderThan :many +-- Same as ListPhotoIDsForHardRetentionOlderThan but only photos currently +-- active as someone's avatar (profile_photos.active) -- lets the Avatar +-- category carry its own retention age, independent of ordinary shared-media +-- photos. +SELECT p.id FROM photos p +WHERE p.created_at < sqlc.arg(cutoff)::timestamptz + AND EXISTS (SELECT 1 FROM profile_photos pp WHERE pp.photo_id = p.id AND pp.active) + AND EXISTS ( + SELECT 1 FROM file_blobs fb + WHERE fb.location_key = 'photo:' || p.id::text + OR fb.location_key LIKE 'photo:' || p.id::text || ':%' + ) +ORDER BY p.created_at ASC +LIMIT sqlc.arg(batch_limit)::int; + +-- name: ListOldestDocumentsForEviction :many +-- Active eviction (TELESRV_STORAGE_EVICTION_ENABLE): candidates are every +-- document that still owns at least one file_blobs row, oldest-uploaded +-- first, REGARDLESS of category or age -- unlike the retention sweeps above, +-- eviction only cares about reclaiming bytes once the total physical budget +-- (TELESRV_STORAGE_MAX_TOTAL_BYTES) is exceeded. created_at is returned so +-- the caller can interleave these with ListOldestPhotosForEviction by actual +-- age instead of draining one table before touching the other. +SELECT d.id, d.created_at FROM documents d +WHERE EXISTS ( + SELECT 1 FROM file_blobs fb + WHERE fb.location_key = 'doc:' || d.id::text + OR fb.location_key LIKE 'doc:' || d.id::text || ':%' + ) +ORDER BY d.created_at ASC +LIMIT sqlc.arg(batch_limit)::int; + +-- name: ListOldestPhotosForEviction :many +-- See ListOldestDocumentsForEviction -- same oldest-first eviction candidate +-- selection, for photos (avatars included: eviction is bytes-only and does +-- not honor the Avatar category's separate retention age). +SELECT p.id, p.created_at FROM photos p +WHERE EXISTS ( + SELECT 1 FROM file_blobs fb + WHERE fb.location_key = 'photo:' || p.id::text + OR fb.location_key LIKE 'photo:' || p.id::text || ':%' + ) +ORDER BY p.created_at ASC +LIMIT sqlc.arg(batch_limit)::int; + -- name: CountFileBlobRefs :one SELECT COUNT(*)::int FROM file_blobs WHERE backend = sqlc.arg(backend)::text AND object_key = sqlc.arg(object_key)::text; diff --git a/internal/store/postgres/retention_v2_integration_test.go b/internal/store/postgres/retention_v2_integration_test.go new file mode 100644 index 00000000..12b66593 --- /dev/null +++ b/internal/store/postgres/retention_v2_integration_test.go @@ -0,0 +1,360 @@ +package postgres + +import ( + "context" + "strconv" + "testing" + "time" + + "telesrv/internal/domain" +) + +// TestRetentionPurgeBypassProducesServiceMessage_PrivateMessage exercises +// Part 1 of the storage retention v2 plan end-to-end against a real +// Postgres: EditMessage with RetentionPurge=true bypasses the ordinary +// author check (the storage retention sweep is not the message's author) and +// replaces the message media with a messageActionCustomAction service +// payload, and a second retention-purge edit on the now-already-service +// message is a no-op (ErrMessageNotModified) -- guarding the idempotency +// guard that keeps a shared media_references duplicate (owner+peer both +// referencing the same purged document) from double-editing the same box. +func TestRetentionPurgeBypassProducesServiceMessage_PrivateMessage(t *testing.T) { + pool := testPool(t) + ctx := context.Background() + suffix := randomSuffix(t) + + users := NewUserStore(pool) + sender, err := users.Create(ctx, domain.User{AccessHash: 81, Phone: "+1997" + suffix + "01", FirstName: "RetentionSender"}) + if err != nil { + t.Fatalf("create sender: %v", err) + } + recipient, err := users.Create(ctx, domain.User{AccessHash: 82, Phone: "+1997" + suffix + "02", FirstName: "RetentionRecipient"}) + if err != nil { + t.Fatalf("create recipient: %v", err) + } + ids := []int64{sender.ID, recipient.ID} + t.Cleanup(func() { + _, _ = pool.Exec(ctx, "DELETE FROM message_boxes WHERE owner_user_id = ANY($1::bigint[])", ids) + _, _ = pool.Exec(ctx, "DELETE FROM private_messages WHERE sender_user_id = ANY($1::bigint[])", ids) + _, _ = pool.Exec(ctx, "DELETE FROM dialogs WHERE user_id = ANY($1::bigint[])", ids) + _, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", ids) + }) + + messages := NewMessageStore(pool, WithMessageAllocators(&perUserCounterAllocator{})) + sent, err := messages.SendPrivateText(ctx, domain.SendPrivateTextRequest{ + SenderUserID: sender.ID, + RecipientUserID: recipient.ID, + RandomID: time.Now().UnixNano(), + Message: "a file worth keeping", + Media: &domain.MessageMedia{ + Kind: domain.MessageMediaKindDocument, + Document: &domain.Document{ID: time.Now().UnixNano(), AccessHash: 1, MimeType: "application/octet-stream"}, + }, + Date: int(time.Now().Unix()), + }) + if err != nil { + t.Fatalf("send private media: %v", err) + } + + noticeMedia := &domain.MessageMedia{ + Kind: domain.MessageMediaKindService, + ServiceAction: &domain.MessageServiceAction{ + Kind: domain.MessageServiceActionCustomText, + Text: domain.RetentionPurgeNoticeText, + }, + } + req := domain.EditMessageRequest{ + OwnerUserID: sender.ID, + Peer: domain.Peer{Type: domain.PeerTypeUser, ID: recipient.ID}, + ID: sent.SenderMessage.ID, + SetRichMessage: true, + Media: noticeMedia, + RetentionPurge: true, + } + res, err := messages.EditMessage(ctx, req) + if err != nil { + t.Fatalf("EditMessage(RetentionPurge): %v", err) + } + self := res.Self() + if self.Message.Media == nil || self.Message.Media.Kind != domain.MessageMediaKindService { + t.Fatalf("edited message media = %+v, want service kind", self.Message.Media) + } + if self.Message.Media.ServiceAction == nil || self.Message.Media.ServiceAction.Kind != domain.MessageServiceActionCustomText { + t.Fatalf("edited message service action = %+v, want custom_text", self.Message.Media.ServiceAction) + } + if self.Message.Media.ServiceAction.Text != domain.RetentionPurgeNoticeText { + t.Fatalf("edited message notice text = %q, want %q", self.Message.Media.ServiceAction.Text, domain.RetentionPurgeNoticeText) + } + + // A second retention-purge edit is a no-op: the message is already the + // notice, so this must not keep bumping pts / re-writing it forever. + if _, err := messages.EditMessage(ctx, req); err != domain.ErrMessageNotModified { + t.Fatalf("second RetentionPurge edit err = %v, want ErrMessageNotModified", err) + } + + // A non-author, non-bypass edit on someone else's message must still be + // rejected -- RetentionPurge only bypasses the author check when it is + // actually set, not for ordinary edits in general. + ordinary := req + ordinary.RetentionPurge = false + ordinary.OwnerUserID = recipient.ID + ordinary.Peer = domain.Peer{Type: domain.PeerTypeUser, ID: sender.ID} + if _, err := messages.EditMessage(ctx, ordinary); err != domain.ErrMessageAuthorRequired { + t.Fatalf("non-bypass edit by non-author err = %v, want ErrMessageAuthorRequired", err) + } +} + +// TestRetentionPurgeBypassProducesServiceMessage_ChannelMessage is the +// channel counterpart of the private-message test above: channel messages +// store their service action in a structurally separate Action column (see +// tgChannelMessage), so applyRetentionPurgeChannelMessage must clear body/ +// media and set Action instead of replacing Media like the private path. +func TestRetentionPurgeBypassProducesServiceMessage_ChannelMessage(t *testing.T) { + pool := testPool(t) + ctx := context.Background() + suffix := randomSuffix(t) + + users := NewUserStore(pool) + owner, err := users.Create(ctx, domain.User{AccessHash: 83, Phone: "+1997" + suffix + "03", FirstName: "RetentionChOwner"}) + if err != nil { + t.Fatalf("create owner: %v", err) + } + t.Cleanup(func() { _, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID) }) + + channels := NewChannelStore(pool) + created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{CreatorUserID: owner.ID, Title: "Retention " + suffix, Megagroup: true, Date: 1700003000}) + if err != nil { + t.Fatalf("create channel: %v", err) + } + channelID := created.Channel.ID + t.Cleanup(func() { _, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = $1", channelID) }) + + sent, err := channels.SendChannelMessage(ctx, domain.SendChannelMessageRequest{ + UserID: owner.ID, ChannelID: channelID, RandomID: 771122, Message: "old file", + Media: &domain.MessageMedia{Kind: domain.MessageMediaKindDocument, Document: &domain.Document{ID: time.Now().UnixNano(), AccessHash: 1}}, + Date: 1700003001, + }) + if err != nil { + t.Fatalf("send channel message: %v", err) + } + + req := domain.EditChannelMessageRequest{ + ChannelID: channelID, + ID: sent.Message.ID, + RetentionPurge: true, + RetentionPurgeAction: &domain.ChannelMessageAction{Type: domain.ChannelActionCustomText, Text: domain.RetentionPurgeNoticeText}, + } + res, err := channels.EditChannelMessage(ctx, req) + if err != nil { + t.Fatalf("EditChannelMessage(RetentionPurge): %v", err) + } + if res.Message.Action == nil || res.Message.Action.Type != domain.ChannelActionCustomText { + t.Fatalf("edited channel message action = %+v, want custom_text", res.Message.Action) + } + if res.Message.Action.Text != domain.RetentionPurgeNoticeText { + t.Fatalf("edited channel message notice text = %q, want %q", res.Message.Action.Text, domain.RetentionPurgeNoticeText) + } + if res.Message.Body != "" || !res.Message.Media.IsZero() { + t.Fatalf("edited channel message body/media not cleared: body=%q media=%+v", res.Message.Body, res.Message.Media) + } + + // Idempotency guard: a second retention-purge edit on an already-service + // message must not succeed again. + if _, err := channels.EditChannelMessage(ctx, req); err != domain.ErrMessageNotModified { + t.Fatalf("second RetentionPurge channel edit err = %v, want ErrMessageNotModified", err) + } + + // Read-back via ListChannelHistory renders as a service message too. + hist, err := channels.ListChannelHistory(ctx, owner.ID, domain.ChannelHistoryFilter{ChannelID: channelID, Limit: 10}) + if err != nil { + t.Fatalf("list channel history: %v", err) + } + var found bool + for _, m := range hist.Messages { + if m.ID == sent.Message.ID { + found = true + if m.Action == nil || m.Action.Type != domain.ChannelActionCustomText { + t.Fatalf("history action = %+v, want custom_text", m.Action) + } + } + } + if !found { + t.Fatalf("purged message %d not found in channel history", sent.Message.ID) + } +} + +// TestDocumentCategoryPopulatedOnSend guards Part 2's schema/write-path +// change: sending a document message stamps the already-computed +// domain.DocumentMediaCategory value directly onto the documents row, at the +// same point the shared message_box_media index is written -- zero new +// classification logic. +func TestDocumentCategoryPopulatedOnSend(t *testing.T) { + pool := testPool(t) + ctx := context.Background() + suffix := randomSuffix(t) + + users := NewUserStore(pool) + sender, err := users.Create(ctx, domain.User{AccessHash: 84, Phone: "+1997" + suffix + "04", FirstName: "CatSender"}) + if err != nil { + t.Fatalf("create sender: %v", err) + } + recipient, err := users.Create(ctx, domain.User{AccessHash: 85, Phone: "+1997" + suffix + "05", FirstName: "CatRecipient"}) + if err != nil { + t.Fatalf("create recipient: %v", err) + } + ids := []int64{sender.ID, recipient.ID} + docID := time.Now().UnixNano() + t.Cleanup(func() { + _, _ = pool.Exec(ctx, "DELETE FROM message_boxes WHERE owner_user_id = ANY($1::bigint[])", ids) + _, _ = pool.Exec(ctx, "DELETE FROM private_messages WHERE sender_user_id = ANY($1::bigint[])", ids) + _, _ = pool.Exec(ctx, "DELETE FROM dialogs WHERE user_id = ANY($1::bigint[])", ids) + _, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", ids) + _, _ = pool.Exec(ctx, "DELETE FROM documents WHERE id = $1", docID) + _, _ = pool.Exec(ctx, "DELETE FROM media_references WHERE media_kind = 'document' AND media_id = $1", docID) + }) + + media := NewMediaStore(pool) + if err := media.PutDocument(ctx, domain.Document{ID: docID, MimeType: "video/mp4", Size: 2048}); err != nil { + t.Fatalf("PutDocument: %v", err) + } + + messages := NewMessageStore(pool, WithMessageAllocators(&perUserCounterAllocator{})) + _, err = messages.SendPrivateText(ctx, domain.SendPrivateTextRequest{ + SenderUserID: sender.ID, + RecipientUserID: recipient.ID, + RandomID: time.Now().UnixNano(), + Media: &domain.MessageMedia{ + Kind: domain.MessageMediaKindDocument, + Document: &domain.Document{ + ID: docID, AccessHash: 1, MimeType: "video/mp4", + Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrVideo, W: 640, H: 480, Duration: 5}}, + }, + }, + Date: int(time.Now().Unix()), + }) + if err != nil { + t.Fatalf("send video document: %v", err) + } + + var category int16 + if err := pool.QueryRow(ctx, "SELECT category FROM documents WHERE id = $1", docID).Scan(&category); err != nil { + t.Fatalf("read document category: %v", err) + } + if domain.MediaCategory(category) != domain.MediaCategoryVideo { + t.Fatalf("document category = %d, want %d (Video)", category, domain.MediaCategoryVideo) + } +} + +// TestHardRetentionCategoryFilterOnlyReturnsMatchingCategory guards Part 2's +// query-layer change: ListDocumentIDsForHardRetentionOlderThan now takes a +// category argument and must only return documents in that exact bucket, +// even when another old, blob-owning document sits in a different category. +func TestHardRetentionCategoryFilterOnlyReturnsMatchingCategory(t *testing.T) { + pool := testPool(t) + ctx := context.Background() + media := NewMediaStore(pool) + + videoID := time.Now().UnixNano() + musicID := videoID + 1 + t.Cleanup(func() { + _, _ = pool.Exec(context.Background(), "DELETE FROM documents WHERE id = ANY($1::bigint[])", []int64{videoID, musicID}) + _, _ = pool.Exec(context.Background(), "DELETE FROM file_blobs WHERE location_key = ANY($1::text[])", + []string{"doc:" + strconv.FormatInt(videoID, 10), "doc:" + strconv.FormatInt(musicID, 10)}) + }) + + for _, d := range []struct { + id int64 + category domain.MediaCategory + }{{videoID, domain.MediaCategoryVideo}, {musicID, domain.MediaCategoryMusic}} { + if err := media.PutDocument(ctx, domain.Document{ID: d.id, MimeType: "application/octet-stream", Size: 512}); err != nil { + t.Fatalf("PutDocument %d: %v", d.id, err) + } + if _, err := pool.Exec(ctx, "UPDATE documents SET created_at = now() - interval '100 days', category = $2 WHERE id = $1", d.id, int16(d.category)); err != nil { + t.Fatalf("backdate/categorize document %d: %v", d.id, err) + } + blob := postgresTestBlob("doc:"+strconv.FormatInt(d.id, 10), "cat-filter", 512, "application/octet-stream") + if err := media.PutFileBlob(ctx, blob); err != nil { + t.Fatalf("PutFileBlob %d: %v", d.id, err) + } + } + + cutoff := time.Now().Add(-24 * time.Hour) + videoIDs, err := media.ListDocumentIDsForHardRetentionOlderThan(ctx, domain.MediaCategoryVideo, cutoff, 1000) + if err != nil { + t.Fatalf("ListDocumentIDsForHardRetentionOlderThan(Video): %v", err) + } + if !containsInt64(videoIDs, videoID) || containsInt64(videoIDs, musicID) { + t.Fatalf("video-category candidates = %v, want to include %d and exclude %d", videoIDs, videoID, musicID) + } + + musicIDs, err := media.ListDocumentIDsForHardRetentionOlderThan(ctx, domain.MediaCategoryMusic, cutoff, 1000) + if err != nil { + t.Fatalf("ListDocumentIDsForHardRetentionOlderThan(Music): %v", err) + } + if !containsInt64(musicIDs, musicID) || containsInt64(musicIDs, videoID) { + t.Fatalf("music-category candidates = %v, want to include %d and exclude %d", musicIDs, musicID, videoID) + } +} + +// TestEvictionListsOldestAcrossDocumentsAndPhotos guards Part 3's eviction +// query layer: ListOldestMediaForEviction must return candidates from both +// tables (interleaving by created_at is done by the caller in Go, see +// files.Service.EvictOldestMediaOverBudget) so the oldest-overall item can be +// picked regardless of which table it lives in. +func TestEvictionListsOldestAcrossDocumentsAndPhotos(t *testing.T) { + pool := testPool(t) + ctx := context.Background() + media := NewMediaStore(pool) + + docID := time.Now().UnixNano() + photoID := docID + 1 + t.Cleanup(func() { + _, _ = pool.Exec(context.Background(), "DELETE FROM documents WHERE id = $1", docID) + _, _ = pool.Exec(context.Background(), "DELETE FROM photos WHERE id = $1", photoID) + _, _ = pool.Exec(context.Background(), "DELETE FROM file_blobs WHERE location_key = ANY($1::text[])", + []string{"doc:" + strconv.FormatInt(docID, 10), "photo:" + strconv.FormatInt(photoID, 10)}) + }) + + if err := media.PutDocument(ctx, domain.Document{ID: docID, MimeType: "application/octet-stream", Size: 256}); err != nil { + t.Fatalf("PutDocument: %v", err) + } + // The document is the older of the two (200 days vs 100 days) -- it must + // sort before the photo in the merged eviction candidate list. + if _, err := pool.Exec(ctx, "UPDATE documents SET created_at = now() - interval '200 days' WHERE id = $1", docID); err != nil { + t.Fatalf("backdate document: %v", err) + } + if err := media.PutFileBlob(ctx, postgresTestBlob("doc:"+strconv.FormatInt(docID, 10), "evict-doc", 256, "application/octet-stream")); err != nil { + t.Fatalf("PutFileBlob doc: %v", err) + } + + if err := media.PutPhoto(ctx, domain.Photo{ID: photoID, AccessHash: 1}); err != nil { + t.Fatalf("PutPhoto: %v", err) + } + if _, err := pool.Exec(ctx, "UPDATE photos SET created_at = now() - interval '100 days' WHERE id = $1", photoID); err != nil { + t.Fatalf("backdate photo: %v", err) + } + if err := media.PutFileBlob(ctx, postgresTestBlob("photo:"+strconv.FormatInt(photoID, 10), "evict-photo", 256, "image/jpeg")); err != nil { + t.Fatalf("PutFileBlob photo: %v", err) + } + + candidates, err := media.ListOldestMediaForEviction(ctx, 1000) + if err != nil { + t.Fatalf("ListOldestMediaForEviction: %v", err) + } + var doc, photo *domain.EvictionCandidate + for i := range candidates { + c := candidates[i] + switch { + case c.Kind == domain.MediaKindDocument && c.MediaID == docID: + doc = &candidates[i] + case c.Kind == domain.MediaKindPhoto && c.MediaID == photoID: + photo = &candidates[i] + } + } + if doc == nil || photo == nil { + t.Fatalf("eviction candidates missing doc/photo: %+v", candidates) + } + if !doc.CreatedAt.Before(photo.CreatedAt) { + t.Fatalf("document created_at %v not before photo created_at %v (document should be older)", doc.CreatedAt, photo.CreatedAt) + } +} diff --git a/internal/store/postgres/sqlcgen/media.sql.go b/internal/store/postgres/sqlcgen/media.sql.go index 81ff1ce6..1ed20a3f 100644 --- a/internal/store/postgres/sqlcgen/media.sql.go +++ b/internal/store/postgres/sqlcgen/media.sql.go @@ -825,20 +825,103 @@ func (q *Queries) ListAvailableReactions(ctx context.Context) ([]AvailableReacti return items, nil } +const listAvatarOrphanedPhotoIDsOlderThan = `-- name: ListAvatarOrphanedPhotoIDsOlderThan :many +SELECT p.id FROM photos p +WHERE p.orphaned_at IS NOT NULL AND p.orphaned_at < $1::timestamptz + AND EXISTS (SELECT 1 FROM profile_photos pp WHERE pp.photo_id = p.id AND pp.active) +ORDER BY p.orphaned_at ASC +LIMIT $2::int +` + +type ListAvatarOrphanedPhotoIDsOlderThanParams struct { + Cutoff pgtype.Timestamptz + BatchLimit int32 +} + +// Same as ListOrphanedPhotoIDsOlderThan but only photos currently active as +// someone's avatar (profile_photos.active) -- lets the Avatar category carry +// its own retention age. In practice a live avatar is never orphaned (an +// active profile_photos row is itself a media_references entry), so this +// bucket is expected to stay empty under "orphan" mode; kept for symmetry +// with the "hard" mode avatar split, which is the one that actually matters. +func (q *Queries) ListAvatarOrphanedPhotoIDsOlderThan(ctx context.Context, arg ListAvatarOrphanedPhotoIDsOlderThanParams) ([]int64, error) { + rows, err := q.db.Query(ctx, listAvatarOrphanedPhotoIDsOlderThan, arg.Cutoff, arg.BatchLimit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAvatarPhotoIDsForHardRetentionOlderThan = `-- name: ListAvatarPhotoIDsForHardRetentionOlderThan :many +SELECT p.id FROM photos p +WHERE p.created_at < $1::timestamptz + AND EXISTS (SELECT 1 FROM profile_photos pp WHERE pp.photo_id = p.id AND pp.active) + AND EXISTS ( + SELECT 1 FROM file_blobs fb + WHERE fb.location_key = 'photo:' || p.id::text + OR fb.location_key LIKE 'photo:' || p.id::text || ':%' + ) +ORDER BY p.created_at ASC +LIMIT $2::int +` + +type ListAvatarPhotoIDsForHardRetentionOlderThanParams struct { + Cutoff pgtype.Timestamptz + BatchLimit int32 +} + +// Same as ListPhotoIDsForHardRetentionOlderThan but only photos currently +// active as someone's avatar (profile_photos.active) -- lets the Avatar +// category carry its own retention age, independent of ordinary shared-media +// photos. +func (q *Queries) ListAvatarPhotoIDsForHardRetentionOlderThan(ctx context.Context, arg ListAvatarPhotoIDsForHardRetentionOlderThanParams) ([]int64, error) { + rows, err := q.db.Query(ctx, listAvatarPhotoIDsForHardRetentionOlderThan, arg.Cutoff, arg.BatchLimit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listDocumentIDsForHardRetentionOlderThan = `-- name: ListDocumentIDsForHardRetentionOlderThan :many SELECT d.id FROM documents d WHERE d.created_at < $1::timestamptz + AND d.category = $2::smallint AND EXISTS ( SELECT 1 FROM file_blobs fb WHERE fb.location_key = 'doc:' || d.id::text OR fb.location_key LIKE 'doc:' || d.id::text || ':%' ) ORDER BY d.created_at ASC -LIMIT $2::int +LIMIT $3::int ` type ListDocumentIDsForHardRetentionOlderThanParams struct { Cutoff pgtype.Timestamptz + Category int16 BatchLimit int32 } @@ -848,9 +931,11 @@ type ListDocumentIDsForHardRetentionOlderThanParams struct { // keeps this sweep from re-selecting the same document forever: once its // blob bytes are purged (DeleteFileBlobsForDocument removes the file_blobs // rows but deliberately leaves this documents row in place), it naturally -// drops out of this query on the next pass. +// drops out of this query on the next pass. category scopes to one +// documents.category bucket per sweep tick -- see +// ListOrphanedDocumentIDsOlderThan for the 0/None fallback note. func (q *Queries) ListDocumentIDsForHardRetentionOlderThan(ctx context.Context, arg ListDocumentIDsForHardRetentionOlderThanParams) ([]int64, error) { - rows, err := q.db.Query(ctx, listDocumentIDsForHardRetentionOlderThan, arg.Cutoff, arg.BatchLimit) + rows, err := q.db.Query(ctx, listDocumentIDsForHardRetentionOlderThan, arg.Cutoff, arg.Category, arg.BatchLimit) if err != nil { return nil, err } @@ -917,20 +1002,156 @@ func (q *Queries) ListFileBlobsByLocationPrefix(ctx context.Context, arg ListFil return items, nil } +const listMediaReferences = `-- name: ListMediaReferences :many +SELECT media_kind, media_id, ref_kind, ref_key +FROM media_references +WHERE media_kind = $1::text AND media_id = $2::bigint +` + +type ListMediaReferencesParams struct { + MediaKind string + MediaID int64 +} + +type ListMediaReferencesRow struct { + MediaKind string + MediaID int64 + RefKind string + RefKey string +} + +// Every live reference to a document/photo -- used by the storage retention +// sweep to turn a hard-retention/eviction blob purge into a visible notice on +// every message that still embeds the purged media (see +// files.notifyRetentionPurge). profile_photo/sticker_set/gift refs have no +// message to edit and are filtered by the caller, not this query. +func (q *Queries) ListMediaReferences(ctx context.Context, arg ListMediaReferencesParams) ([]ListMediaReferencesRow, error) { + rows, err := q.db.Query(ctx, listMediaReferences, arg.MediaKind, arg.MediaID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListMediaReferencesRow + for rows.Next() { + var i ListMediaReferencesRow + if err := rows.Scan( + &i.MediaKind, + &i.MediaID, + &i.RefKind, + &i.RefKey, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listOldestDocumentsForEviction = `-- name: ListOldestDocumentsForEviction :many +SELECT d.id, d.created_at FROM documents d +WHERE EXISTS ( + SELECT 1 FROM file_blobs fb + WHERE fb.location_key = 'doc:' || d.id::text + OR fb.location_key LIKE 'doc:' || d.id::text || ':%' + ) +ORDER BY d.created_at ASC +LIMIT $1::int +` + +type ListOldestDocumentsForEvictionRow struct { + ID int64 + CreatedAt pgtype.Timestamptz +} + +// Active eviction (TELESRV_STORAGE_EVICTION_ENABLE): candidates are every +// document that still owns at least one file_blobs row, oldest-uploaded +// first, REGARDLESS of category or age -- unlike the retention sweeps above, +// eviction only cares about reclaiming bytes once the total physical budget +// (TELESRV_STORAGE_MAX_TOTAL_BYTES) is exceeded. created_at is returned so +// the caller can interleave these with ListOldestPhotosForEviction by actual +// age instead of draining one table before touching the other. +func (q *Queries) ListOldestDocumentsForEviction(ctx context.Context, batchLimit int32) ([]ListOldestDocumentsForEvictionRow, error) { + rows, err := q.db.Query(ctx, listOldestDocumentsForEviction, batchLimit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListOldestDocumentsForEvictionRow + for rows.Next() { + var i ListOldestDocumentsForEvictionRow + if err := rows.Scan(&i.ID, &i.CreatedAt); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listOldestPhotosForEviction = `-- name: ListOldestPhotosForEviction :many +SELECT p.id, p.created_at FROM photos p +WHERE EXISTS ( + SELECT 1 FROM file_blobs fb + WHERE fb.location_key = 'photo:' || p.id::text + OR fb.location_key LIKE 'photo:' || p.id::text || ':%' + ) +ORDER BY p.created_at ASC +LIMIT $1::int +` + +type ListOldestPhotosForEvictionRow struct { + ID int64 + CreatedAt pgtype.Timestamptz +} + +// See ListOldestDocumentsForEviction -- same oldest-first eviction candidate +// selection, for photos (avatars included: eviction is bytes-only and does +// not honor the Avatar category's separate retention age). +func (q *Queries) ListOldestPhotosForEviction(ctx context.Context, batchLimit int32) ([]ListOldestPhotosForEvictionRow, error) { + rows, err := q.db.Query(ctx, listOldestPhotosForEviction, batchLimit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListOldestPhotosForEvictionRow + for rows.Next() { + var i ListOldestPhotosForEvictionRow + if err := rows.Scan(&i.ID, &i.CreatedAt); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listOrphanedDocumentIDsOlderThan = `-- name: ListOrphanedDocumentIDsOlderThan :many SELECT id FROM documents WHERE orphaned_at IS NOT NULL AND orphaned_at < $1::timestamptz + AND category = $2::smallint ORDER BY orphaned_at ASC -LIMIT $2::int +LIMIT $3::int ` type ListOrphanedDocumentIDsOlderThanParams struct { Cutoff pgtype.Timestamptz + Category int16 BatchLimit int32 } +// category selects one documents.category bucket per sweep tick (per-category +// retention age, see internal/app/files/retention.go) -- 0 (MediaCategoryNone) +// covers unclassified documents (e.g. stickers), which always use the shared +// global age since there is no per-category override for that bucket. func (q *Queries) ListOrphanedDocumentIDsOlderThan(ctx context.Context, arg ListOrphanedDocumentIDsOlderThanParams) ([]int64, error) { - rows, err := q.db.Query(ctx, listOrphanedDocumentIDsOlderThan, arg.Cutoff, arg.BatchLimit) + rows, err := q.db.Query(ctx, listOrphanedDocumentIDsOlderThan, arg.Cutoff, arg.Category, arg.BatchLimit) if err != nil { return nil, err } @@ -950,9 +1171,10 @@ func (q *Queries) ListOrphanedDocumentIDsOlderThan(ctx context.Context, arg List } const listOrphanedPhotoIDsOlderThan = `-- name: ListOrphanedPhotoIDsOlderThan :many -SELECT id FROM photos -WHERE orphaned_at IS NOT NULL AND orphaned_at < $1::timestamptz -ORDER BY orphaned_at ASC +SELECT p.id FROM photos p +WHERE p.orphaned_at IS NOT NULL AND p.orphaned_at < $1::timestamptz + AND NOT EXISTS (SELECT 1 FROM profile_photos pp WHERE pp.photo_id = p.id AND pp.active) +ORDER BY p.orphaned_at ASC LIMIT $2::int ` @@ -961,6 +1183,8 @@ type ListOrphanedPhotoIDsOlderThanParams struct { BatchLimit int32 } +// Excludes photos currently active as someone's avatar -- see +// ListAvatarOrphanedPhotoIDsOlderThan for that split-off bucket. func (q *Queries) ListOrphanedPhotoIDsOlderThan(ctx context.Context, arg ListOrphanedPhotoIDsOlderThanParams) ([]int64, error) { rows, err := q.db.Query(ctx, listOrphanedPhotoIDsOlderThan, arg.Cutoff, arg.BatchLimit) if err != nil { @@ -984,6 +1208,7 @@ func (q *Queries) ListOrphanedPhotoIDsOlderThan(ctx context.Context, arg ListOrp const listPhotoIDsForHardRetentionOlderThan = `-- name: ListPhotoIDsForHardRetentionOlderThan :many SELECT p.id FROM photos p WHERE p.created_at < $1::timestamptz + AND NOT EXISTS (SELECT 1 FROM profile_photos pp WHERE pp.photo_id = p.id AND pp.active) AND EXISTS ( SELECT 1 FROM file_blobs fb WHERE fb.location_key = 'photo:' || p.id::text @@ -999,7 +1224,8 @@ type ListPhotoIDsForHardRetentionOlderThanParams struct { } // See ListDocumentIDsForHardRetentionOlderThan -- same "hard" retention -// candidate selection, for photos. +// candidate selection, for photos. Excludes photos currently active as +// someone's avatar -- see ListAvatarPhotoIDsForHardRetentionOlderThan. func (q *Queries) ListPhotoIDsForHardRetentionOlderThan(ctx context.Context, arg ListPhotoIDsForHardRetentionOlderThanParams) ([]int64, error) { rows, err := q.db.Query(ctx, listPhotoIDsForHardRetentionOlderThan, arg.Cutoff, arg.BatchLimit) if err != nil { diff --git a/internal/store/postgres/sqlcgen/models.go b/internal/store/postgres/sqlcgen/models.go index 919f4fff..6fc2a2f0 100644 --- a/internal/store/postgres/sqlcgen/models.go +++ b/internal/store/postgres/sqlcgen/models.go @@ -1252,6 +1252,7 @@ type Document struct { CreatedAt pgtype.Timestamptz OwnerUserID int64 OrphanedAt pgtype.Timestamptz + Category int16 } type EncryptedFile struct {