diff --git a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/assets/index-CCGOrIw_.js b/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/assets/index-CCGOrIw_.js deleted file mode 100644 index c896bf0..0000000 --- a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/assets/index-CCGOrIw_.js +++ /dev/null @@ -1 +0,0 @@ -function C(o){return o&&o.__esModule&&Object.prototype.hasOwnProperty.call(o,"default")?o.default:o}var b={exports:{}},I;function O(){return I||(I=1,function(o){var e=Object.prototype.hasOwnProperty,t="~";function u(){}Object.create&&(u.prototype=Object.create(null),new u().__proto__||(t=!1));function d(h,i,n){this.fn=h,this.context=i,this.once=n||!1}function l(h,i,n,r,v){if(typeof n!="function")throw new TypeError("The listener must be a function");var a=new d(n,r||h,v),f=t?t+i:i;return h._events[f]?h._events[f].fn?h._events[f]=[h._events[f],a]:h._events[f].push(a):(h._events[f]=a,h._eventsCount++),h}function y(h,i){--h._eventsCount===0?h._events=new u:delete h._events[i]}function c(){this._events=new u,this._eventsCount=0}c.prototype.eventNames=function(){var i=[],n,r;if(this._eventsCount===0)return i;for(r in n=this._events)e.call(n,r)&&i.push(t?r.slice(1):r);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(n)):i},c.prototype.listeners=function(i){var n=t?t+i:i,r=this._events[n];if(!r)return[];if(r.fn)return[r.fn];for(var v=0,a=r.length,f=new Array(a);vglobalThis.DOMException===void 0?new q(o):new DOMException(o),x=o=>{const e=o.reason===void 0?_("This operation was aborted."):o.reason;return e instanceof Error?e:_(e)};function z(o,e){const{milliseconds:t,fallback:u,message:d,customTimers:l={setTimeout,clearTimeout}}=e;let y,c;const i=new Promise((n,r)=>{if(typeof t!="number"||Math.sign(t)!==1)throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${t}\``);if(e.signal){const{signal:a}=e;a.aborted&&r(x(a)),c=()=>{r(x(a))},a.addEventListener("abort",c,{once:!0})}if(t===Number.POSITIVE_INFINITY){o.then(n,r);return}const v=new T;y=l.setTimeout.call(void 0,()=>{if(u){try{n(u())}catch(a){r(a)}return}typeof o.cancel=="function"&&o.cancel(),d===!1?n():d instanceof Error?r(d):(v.message=d??`Promise timed out after ${t} milliseconds`,r(v))},t),(async()=>{try{n(await o)}catch(a){r(a)}})()}).finally(()=>{i.clear(),c&&e.signal&&e.signal.removeEventListener("abort",c)});return i.clear=()=>{l.clearTimeout.call(void 0,y),y=void 0},i}function L(o,e,t){let u=0,d=o.length;for(;d>0;){const l=Math.trunc(d/2);let y=u+l;t(o[y],e)<=0?(u=++y,d-=l+1):d=l}return u}class S{#e=[];enqueue(e,t){t={priority:0,...t};const u={priority:t.priority,id:t.id,run:e};if(this.size===0||this.#e[this.size-1].priority>=t.priority){this.#e.push(u);return}const d=L(this.#e,u,(l,y)=>y.priority-l.priority);this.#e.splice(d,0,u)}setPriority(e,t){const u=this.#e.findIndex(l=>l.id===e);if(u===-1)throw new ReferenceError(`No promise function with the id "${e}" exists in the queue.`);const[d]=this.#e.splice(u,1);this.enqueue(d.run,{priority:t,id:e})}dequeue(){return this.#e.shift()?.run}filter(e){return this.#e.filter(t=>t.priority===e.priority).map(t=>t.run)}get size(){return this.#e.length}}class $ extends N{#e;#u;#s=0;#d;#o;#m=0;#r;#a;#t;#v;#n=0;#h;#i;#y;#g=1n;timeout;constructor(e){if(super(),e={carryoverConcurrencyCount:!1,intervalCap:Number.POSITIVE_INFINITY,interval:0,concurrency:Number.POSITIVE_INFINITY,autoStart:!0,queueClass:S,...e},!(typeof e.intervalCap=="number"&&e.intervalCap>=1))throw new TypeError(`Expected \`intervalCap\` to be a number from 1 and up, got \`${e.intervalCap?.toString()??""}\` (${typeof e.intervalCap})`);if(e.interval===void 0||!(Number.isFinite(e.interval)&&e.interval>=0))throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${e.interval?.toString()??""}\` (${typeof e.interval})`);this.#e=e.carryoverConcurrencyCount,this.#u=e.intervalCap===Number.POSITIVE_INFINITY||e.interval===0,this.#d=e.intervalCap,this.#o=e.interval,this.#t=new e.queueClass,this.#v=e.queueClass,this.concurrency=e.concurrency,this.timeout=e.timeout,this.#y=e.throwOnTimeout===!0,this.#i=e.autoStart===!1}get#E(){return this.#u||this.#s{this.#_()},t)),!0}return!1}#l(){if(this.#t.size===0)return this.#r&&clearInterval(this.#r),this.#r=void 0,this.emit("empty"),this.#n===0&&this.emit("idle"),!1;if(!this.#i){const e=!this.#x;if(this.#E&&this.#b){const t=this.#t.dequeue();return t?(this.emit("active"),t(),e&&this.#p(),!0):!1}}return!1}#p(){this.#u||this.#r!==void 0||(this.#r=setInterval(()=>{this.#w()},this.#o),this.#m=Date.now()+this.#o)}#w(){this.#s===0&&this.#n===0&&this.#r&&(clearInterval(this.#r),this.#r=void 0),this.#s=this.#e?this.#n:0,this.#c()}#c(){for(;this.#l(););}get concurrency(){return this.#h}set concurrency(e){if(!(typeof e=="number"&&e>=1))throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${e}\` (${typeof e})`);this.#h=e,this.#c()}async#T(e){return new Promise((t,u)=>{e.addEventListener("abort",()=>{u(e.reason)},{once:!0})})}setPriority(e,t){this.#t.setPriority(e,t)}async add(e,t={}){return t.id??=(this.#g++).toString(),t={timeout:this.timeout,throwOnTimeout:this.#y,...t},new Promise((u,d)=>{this.#t.enqueue(async()=>{this.#n++,this.#s++;try{t.signal?.throwIfAborted();let l=e({signal:t.signal});t.timeout&&(l=z(Promise.resolve(l),{milliseconds:t.timeout})),t.signal&&(l=Promise.race([l,this.#T(t.signal)]));const y=await l;u(y),this.emit("completed",y)}catch(l){if(l instanceof T&&!t.throwOnTimeout){u();return}d(l),this.emit("error",l)}finally{this.#I()}},t),this.emit("add"),this.#l()})}async addAll(e,t){return Promise.all(e.map(async u=>this.add(u,t)))}start(){return this.#i?(this.#i=!1,this.#c(),this):this}pause(){this.#i=!0}clear(){this.#t=new this.#v}async onEmpty(){this.#t.size!==0&&await this.#f("empty")}async onSizeLessThan(e){this.#t.sizethis.#t.size{const d=()=>{t&&!t()||(this.off(e,d),u())};this.on(e,d)})}get size(){return this.#t.size}sizeBy(e){return this.#t.filter(e).length}get pending(){return this.#n}get isPaused(){return this.#i}}export{$ as P}; diff --git a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/background.js b/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/background.js index 392190a..d86df64 100644 --- a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/background.js +++ b/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/background.js @@ -1 +1 @@ -function O(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}var E={exports:{}},T;function N(){return T||(T=1,function(r){var e=Object.prototype.hasOwnProperty,t="~";function o(){}Object.create&&(o.prototype=Object.create(null),new o().__proto__||(t=!1));function c(l,s,n){this.fn=l,this.context=s,this.once=n||!1}function u(l,s,n,i,p){if(typeof n!="function")throw new TypeError("The listener must be a function");var f=new c(n,i||l,p),m=t?t+s:s;return l._events[m]?l._events[m].fn?l._events[m]=[l._events[m],f]:l._events[m].push(f):(l._events[m]=f,l._eventsCount++),l}function d(l,s){--l._eventsCount===0?l._events=new o:delete l._events[s]}function h(){this._events=new o,this._eventsCount=0}h.prototype.eventNames=function(){var s=[],n,i;if(this._eventsCount===0)return s;for(i in n=this._events)e.call(n,i)&&s.push(t?i.slice(1):i);return Object.getOwnPropertySymbols?s.concat(Object.getOwnPropertySymbols(n)):s},h.prototype.listeners=function(s){var n=t?t+s:s,i=this._events[n];if(!i)return[];if(i.fn)return[i.fn];for(var p=0,f=i.length,m=new Array(f);pglobalThis.DOMException===void 0?new z(r):new DOMException(r),x=r=>{const e=r.reason===void 0?_("This operation was aborted."):r.reason;return e instanceof Error?e:_(e)};function $(r,e){const{milliseconds:t,fallback:o,message:c,customTimers:u={setTimeout,clearTimeout}}=e;let d,h;const s=new Promise((n,i)=>{if(typeof t!="number"||Math.sign(t)!==1)throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${t}\``);if(e.signal){const{signal:f}=e;f.aborted&&i(x(f)),h=()=>{i(x(f))},f.addEventListener("abort",h,{once:!0})}if(t===Number.POSITIVE_INFINITY){r.then(n,i);return}const p=new C;d=u.setTimeout.call(void 0,()=>{if(o){try{n(o())}catch(f){i(f)}return}typeof r.cancel=="function"&&r.cancel(),c===!1?n():c instanceof Error?i(c):(p.message=c??`Promise timed out after ${t} milliseconds`,i(p))},t),(async()=>{try{n(await r)}catch(f){i(f)}})()}).finally(()=>{s.clear(),h&&e.signal&&e.signal.removeEventListener("abort",h)});return s.clear=()=>{u.clearTimeout.call(void 0,d),d=void 0},s}function k(r,e,t){let o=0,c=r.length;for(;c>0;){const u=Math.trunc(c/2);let d=o+u;t(r[d],e)<=0?(o=++d,c-=u+1):c=u}return o}class D{#e=[];enqueue(e,t){t={priority:0,...t};const o={priority:t.priority,id:t.id,run:e};if(this.size===0||this.#e[this.size-1].priority>=t.priority){this.#e.push(o);return}const c=k(this.#e,o,(u,d)=>d.priority-u.priority);this.#e.splice(c,0,o)}setPriority(e,t){const o=this.#e.findIndex(u=>u.id===e);if(o===-1)throw new ReferenceError(`No promise function with the id "${e}" exists in the queue.`);const[c]=this.#e.splice(o,1);this.enqueue(c.run,{priority:t,id:e})}dequeue(){return this.#e.shift()?.run}filter(e){return this.#e.filter(t=>t.priority===e.priority).map(t=>t.run)}get size(){return this.#e.length}}class M extends q{#e;#o;#s=0;#d;#a;#m=0;#r;#u;#t;#v;#n=0;#c;#i;#p;#b=1n;timeout;constructor(e){if(super(),e={carryoverConcurrencyCount:!1,intervalCap:Number.POSITIVE_INFINITY,interval:0,concurrency:Number.POSITIVE_INFINITY,autoStart:!0,queueClass:D,...e},!(typeof e.intervalCap=="number"&&e.intervalCap>=1))throw new TypeError(`Expected \`intervalCap\` to be a number from 1 and up, got \`${e.intervalCap?.toString()??""}\` (${typeof e.intervalCap})`);if(e.interval===void 0||!(Number.isFinite(e.interval)&&e.interval>=0))throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${e.interval?.toString()??""}\` (${typeof e.interval})`);this.#e=e.carryoverConcurrencyCount,this.#o=e.intervalCap===Number.POSITIVE_INFINITY||e.interval===0,this.#d=e.intervalCap,this.#a=e.interval,this.#t=new e.queueClass,this.#v=e.queueClass,this.concurrency=e.concurrency,this.timeout=e.timeout,this.#p=e.throwOnTimeout===!0,this.#i=e.autoStart===!1}get#g(){return this.#o||this.#s{this.#T()},t)),!0}return!1}#l(){if(this.#t.size===0)return this.#r&&clearInterval(this.#r),this.#r=void 0,this.emit("empty"),this.#n===0&&this.emit("idle"),!1;if(!this.#i){const e=!this.#_;if(this.#g&&this.#E){const t=this.#t.dequeue();return t?(this.emit("active"),t(),e&&this.#y(),!0):!1}}return!1}#y(){this.#o||this.#r!==void 0||(this.#r=setInterval(()=>{this.#w()},this.#a),this.#m=Date.now()+this.#a)}#w(){this.#s===0&&this.#n===0&&this.#r&&(clearInterval(this.#r),this.#r=void 0),this.#s=this.#e?this.#n:0,this.#h()}#h(){for(;this.#l(););}get concurrency(){return this.#c}set concurrency(e){if(!(typeof e=="number"&&e>=1))throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${e}\` (${typeof e})`);this.#c=e,this.#h()}async#x(e){return new Promise((t,o)=>{e.addEventListener("abort",()=>{o(e.reason)},{once:!0})})}setPriority(e,t){this.#t.setPriority(e,t)}async add(e,t={}){return t.id??=(this.#b++).toString(),t={timeout:this.timeout,throwOnTimeout:this.#p,...t},new Promise((o,c)=>{this.#t.enqueue(async()=>{this.#n++,this.#s++;try{t.signal?.throwIfAborted();let u=e({signal:t.signal});t.timeout&&(u=$(Promise.resolve(u),{milliseconds:t.timeout})),t.signal&&(u=Promise.race([u,this.#x(t.signal)]));const d=await u;o(d),this.emit("completed",d)}catch(u){if(u instanceof C&&!t.throwOnTimeout){o();return}c(u),this.emit("error",u)}finally{this.#I()}},t),this.emit("add"),this.#l()})}async addAll(e,t){return Promise.all(e.map(async o=>this.add(o,t)))}start(){return this.#i?(this.#i=!1,this.#h(),this):this}pause(){this.#i=!0}clear(){this.#t=new this.#v}async onEmpty(){this.#t.size!==0&&await this.#f("empty")}async onSizeLessThan(e){this.#t.sizethis.#t.size{const c=()=>{t&&!t()||(this.off(e,c),o())};this.on(e,c)})}get size(){return this.#t.size}sizeBy(e){return this.#t.filter(e).length}get pending(){return this.#n}get isPaused(){return this.#i}}let I=[];const P=new M({concurrency:1});chrome.runtime.onConnect.addListener(r=>{I.push(r),r.onDisconnect.addListener(()=>{I=I.filter(e=>e!==r)})});async function R(r,e=5*60*1e3){return new Promise(t=>{chrome.tabs.create({url:"https://www.facebook.com/marketplace/create/item",active:!0},o=>{if(!o?.id)return t();const c=o.id;let u=!1;const d=()=>{u||(u=!0,chrome.runtime.onConnect.removeListener(h),chrome.tabs.onRemoved.removeListener(l),clearTimeout(s),t(),S())},h=n=>{n.sender?.tab?.id===c&&(n.postMessage({type:"publist-event",payload:r}),chrome.runtime.onConnect.removeListener(h))};chrome.runtime.onConnect.addListener(h);const l=n=>{n===c&&d()};chrome.tabs.onRemoved.addListener(l);const s=setTimeout(()=>{console.warn(`Tab ${c} timeout (${e}ms) - auto resolve`),c&&chrome.tabs.remove(c),d()},e)})})}const F=()=>{const r=new EventSource("http://localhost:4000/api/v1/products/publist-stream");r.onmessage=e=>{const t=JSON.parse(e.data);console.log("New event:",t),P.add(()=>R(t,3e4))},r.onerror=e=>{console.error("EventSource failed:",e)}},Q=()=>{const r=new EventSource("http://localhost:4000/api/v1/products/delete-stream");r.onmessage=e=>{try{const t=JSON.parse(e.data);console.log("[SSE] New event:",t),P.add(()=>new Promise(o=>{const c=u=>{u?.type==="delete-done"&&(console.log("[QUEUE] Delete done signal received."),chrome.runtime.onMessage.removeListener(c),o())};chrome.runtime.onMessage.addListener(c),chrome.tabs.query({url:"https://www.facebook.com/marketplace/you/selling"},u=>{for(const d of u)d.id&&chrome.tabs.sendMessage(d.id,{type:"DELETE_STREAM_DATA",payload:t})})}))}catch(t){console.error("[SSE] Parse error:",t)}},r.onerror=e=>{console.error("[SSE] Connection failed:",e)},console.log("[SSE] Listening for delete-stream events...")};function S(){const r="https://www.facebook.com/marketplace/you/selling";console.log({targetUrl:r}),chrome.tabs.query({},e=>{const t=e.find(o=>o.url&&o.url.startsWith(r));t?chrome.tabs.reload(t.id):chrome.tabs.create({url:r})})}const V=async()=>{F(),Q(),S()};chrome.runtime.onMessage.addListener((r,e)=>{if(r.type==="close-tab"){const t=e.tab?.id;t&&chrome.tabs.remove(t)}});V(); +function N(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}var E={exports:{}},L;function O(){return L||(L=1,function(r){var e=Object.prototype.hasOwnProperty,t="~";function o(){}Object.create&&(o.prototype=Object.create(null),new o().__proto__||(t=!1));function a(l,n,s){this.fn=l,this.context=n,this.once=s||!1}function c(l,n,s,i,p){if(typeof s!="function")throw new TypeError("The listener must be a function");var d=new a(s,i||l,p),m=t?t+n:n;return l._events[m]?l._events[m].fn?l._events[m]=[l._events[m],d]:l._events[m].push(d):(l._events[m]=d,l._eventsCount++),l}function f(l,n){--l._eventsCount===0?l._events=new o:delete l._events[n]}function h(){this._events=new o,this._eventsCount=0}h.prototype.eventNames=function(){var n=[],s,i;if(this._eventsCount===0)return n;for(i in s=this._events)e.call(s,i)&&n.push(t?i.slice(1):i);return Object.getOwnPropertySymbols?n.concat(Object.getOwnPropertySymbols(s)):n},h.prototype.listeners=function(n){var s=t?t+n:n,i=this._events[s];if(!i)return[];if(i.fn)return[i.fn];for(var p=0,d=i.length,m=new Array(d);pglobalThis.DOMException===void 0?new $(r):new DOMException(r),P=r=>{const e=r.reason===void 0?S("This operation was aborted."):r.reason;return e instanceof Error?e:S(e)};function k(r,e){const{milliseconds:t,fallback:o,message:a,customTimers:c={setTimeout,clearTimeout}}=e;let f,h;const n=new Promise((s,i)=>{if(typeof t!="number"||Math.sign(t)!==1)throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${t}\``);if(e.signal){const{signal:d}=e;d.aborted&&i(P(d)),h=()=>{i(P(d))},d.addEventListener("abort",h,{once:!0})}if(t===Number.POSITIVE_INFINITY){r.then(s,i);return}const p=new C;f=c.setTimeout.call(void 0,()=>{if(o){try{s(o())}catch(d){i(d)}return}typeof r.cancel=="function"&&r.cancel(),a===!1?s():a instanceof Error?i(a):(p.message=a??`Promise timed out after ${t} milliseconds`,i(p))},t),(async()=>{try{s(await r)}catch(d){i(d)}})()}).finally(()=>{n.clear(),h&&e.signal&&e.signal.removeEventListener("abort",h)});return n.clear=()=>{c.clearTimeout.call(void 0,f),f=void 0},n}function D(r,e,t){let o=0,a=r.length;for(;a>0;){const c=Math.trunc(a/2);let f=o+c;t(r[f],e)<=0?(o=++f,a-=c+1):a=c}return o}class M{#e=[];enqueue(e,t){t={priority:0,...t};const o={priority:t.priority,id:t.id,run:e};if(this.size===0||this.#e[this.size-1].priority>=t.priority){this.#e.push(o);return}const a=D(this.#e,o,(c,f)=>f.priority-c.priority);this.#e.splice(a,0,o)}setPriority(e,t){const o=this.#e.findIndex(c=>c.id===e);if(o===-1)throw new ReferenceError(`No promise function with the id "${e}" exists in the queue.`);const[a]=this.#e.splice(o,1);this.enqueue(a.run,{priority:t,id:e})}dequeue(){return this.#e.shift()?.run}filter(e){return this.#e.filter(t=>t.priority===e.priority).map(t=>t.run)}get size(){return this.#e.length}}class z extends q{#e;#o;#i=0;#d;#a;#m=0;#r;#c;#t;#v;#n=0;#u;#s;#p;#b=1n;timeout;constructor(e){if(super(),e={carryoverConcurrencyCount:!1,intervalCap:Number.POSITIVE_INFINITY,interval:0,concurrency:Number.POSITIVE_INFINITY,autoStart:!0,queueClass:M,...e},!(typeof e.intervalCap=="number"&&e.intervalCap>=1))throw new TypeError(`Expected \`intervalCap\` to be a number from 1 and up, got \`${e.intervalCap?.toString()??""}\` (${typeof e.intervalCap})`);if(e.interval===void 0||!(Number.isFinite(e.interval)&&e.interval>=0))throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${e.interval?.toString()??""}\` (${typeof e.interval})`);this.#e=e.carryoverConcurrencyCount,this.#o=e.intervalCap===Number.POSITIVE_INFINITY||e.interval===0,this.#d=e.intervalCap,this.#a=e.interval,this.#t=new e.queueClass,this.#v=e.queueClass,this.concurrency=e.concurrency,this.timeout=e.timeout,this.#p=e.throwOnTimeout===!0,this.#s=e.autoStart===!1}get#g(){return this.#o||this.#i{this.#I()},t)),!0}return!1}#l(){if(this.#t.size===0)return this.#r&&clearInterval(this.#r),this.#r=void 0,this.emit("empty"),this.#n===0&&this.emit("idle"),!1;if(!this.#s){const e=!this.#_;if(this.#g&&this.#E){const t=this.#t.dequeue();return t?(this.emit("active"),t(),e&&this.#y(),!0):!1}}return!1}#y(){this.#o||this.#r!==void 0||(this.#r=setInterval(()=>{this.#w()},this.#a),this.#m=Date.now()+this.#a)}#w(){this.#i===0&&this.#n===0&&this.#r&&(clearInterval(this.#r),this.#r=void 0),this.#i=this.#e?this.#n:0,this.#h()}#h(){for(;this.#l(););}get concurrency(){return this.#u}set concurrency(e){if(!(typeof e=="number"&&e>=1))throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${e}\` (${typeof e})`);this.#u=e,this.#h()}async#L(e){return new Promise((t,o)=>{e.addEventListener("abort",()=>{o(e.reason)},{once:!0})})}setPriority(e,t){this.#t.setPriority(e,t)}async add(e,t={}){return t.id??=(this.#b++).toString(),t={timeout:this.timeout,throwOnTimeout:this.#p,...t},new Promise((o,a)=>{this.#t.enqueue(async()=>{this.#n++,this.#i++;try{t.signal?.throwIfAborted();let c=e({signal:t.signal});t.timeout&&(c=k(Promise.resolve(c),{milliseconds:t.timeout})),t.signal&&(c=Promise.race([c,this.#L(t.signal)]));const f=await c;o(f),this.emit("completed",f)}catch(c){if(c instanceof C&&!t.throwOnTimeout){o();return}a(c),this.emit("error",c)}finally{this.#T()}},t),this.emit("add"),this.#l()})}async addAll(e,t){return Promise.all(e.map(async o=>this.add(o,t)))}start(){return this.#s?(this.#s=!1,this.#h(),this):this}pause(){this.#s=!0}clear(){this.#t=new this.#v}async onEmpty(){this.#t.size!==0&&await this.#f("empty")}async onSizeLessThan(e){this.#t.sizethis.#t.size{const a=()=>{t&&!t()||(this.off(e,a),o())};this.on(e,a)})}get size(){return this.#t.size}sizeBy(e){return this.#t.filter(e).length}get pending(){return this.#n}get isPaused(){return this.#s}}let T=[];const I=new z({concurrency:1});chrome.runtime.onConnect.addListener(r=>{T.push(r),r.onDisconnect.addListener(()=>{T=T.filter(e=>e!==r)})});async function R(r,e=5*60*1e3){return new Promise(t=>{chrome.tabs.create({url:"https://www.facebook.com/marketplace/create/item",active:!0},o=>{if(!o?.id)return t();const a=o.id;let c=!1;const f=()=>{c||(c=!0,chrome.runtime.onConnect.removeListener(h),chrome.tabs.onRemoved.removeListener(l),clearTimeout(n),t(),_())},h=s=>{s.sender?.tab?.id===a&&(s.postMessage({type:"PUBLIST_EVENT",payload:r}),chrome.runtime.onConnect.removeListener(h))};chrome.runtime.onConnect.addListener(h);const l=s=>{s===a&&f()};chrome.tabs.onRemoved.addListener(l);const n=setTimeout(()=>{console.warn(`Tab ${a} timeout (${e}ms) - auto resolve`),a&&chrome.tabs.remove(a),f()},e)})})}async function U(r,e=5*60*1e3){return new Promise((t,o)=>{(async()=>{try{if(!r.prev?.publist_id){const a=await F({type:"GET_PUBLIST_ID",data:r},3e4);console.log({response:a}),r.prev.publist_id=a.publist_id}chrome.tabs.create({url:`https://www.facebook.com/marketplace/edit/?listing_id=${r.prev.publist_id}`,active:!0},a=>{a?.id||t(r);const c=a.id;let f=!1;const h=()=>{f||(f=!0,chrome.runtime.onConnect.removeListener(l),chrome.tabs.onRemoved.removeListener(n),clearTimeout(s),t(r),_())},l=i=>{i.sender?.tab?.id===c&&(i.postMessage({type:"EDIT_EVENT",payload:r}),chrome.runtime.onConnect.removeListener(l))};chrome.runtime.onConnect.addListener(l);const n=i=>{i===c&&h()};chrome.tabs.onRemoved.addListener(n);const s=setTimeout(()=>{console.warn(`Tab ${c} timeout (${e}ms) - auto resolve`),c&&chrome.tabs.remove(c),h()},e)})}catch(a){o(a)}})()})}const B=()=>{const r=new EventSource("http://localhost:4000/api/v1/products/publist-stream");r.onmessage=e=>{const t=JSON.parse(e.data);console.log("[PUBLIST] New event:",t),I.add(()=>R(t,3e4))},r.onerror=e=>{console.error("EventSource failed:",e)}};function F(r,e=1e4){return new Promise((t,o)=>{chrome.tabs.query({url:"*://www.facebook.com/marketplace/you/selling*"},a=>{if(a.length>0&&a[0].id!==void 0){const c=a[0].id,f=n=>{n?.type==="GET_PUBLIST_ID_DONE"&&(h(),t(n))},h=()=>{chrome.runtime.onMessage.removeListener(f),clearTimeout(l)};chrome.runtime.onMessage.addListener(f);const l=setTimeout(()=>{h(),o(new Error("Timeout chờ phản hồi từ content script"))},e);chrome.tabs.sendMessage(c,r)}else o(new Error("Không tìm thấy tab Selling"))})})}const V=()=>{const r=new EventSource("http://localhost:4000/api/v1/products/delete-stream");r.onmessage=e=>{try{const t=JSON.parse(e.data);console.log("[SSE] New event:",t),I.add(()=>new Promise(o=>{const a=c=>{c?.type==="delete-done"&&(console.log("[QUEUE] Delete done signal received."),chrome.runtime.onMessage.removeListener(a),o())};chrome.runtime.onMessage.addListener(a),chrome.tabs.query({url:"https://www.facebook.com/marketplace/you/selling"},c=>{for(const f of c)f.id&&chrome.tabs.sendMessage(f.id,{type:"DELETE_STREAM_DATA",payload:t})})}))}catch(t){console.error("[SSE] Parse error:",t)}},r.onerror=e=>{console.error("[SSE] Connection failed:",e)},console.log("[SSE] Listening for delete-stream events...")},Q=()=>{const r=new EventSource("http://localhost:4000/api/v1/products/edit-stream");r.onmessage=e=>{const t=JSON.parse(e.data);console.log("[EDIT] New event:",t),I.add(()=>U(t,3e4))},r.onerror=e=>{console.error("EventSource failed:",e)}};function _(){const r="https://www.facebook.com/marketplace/you/selling";console.log({targetUrl:r}),chrome.tabs.query({},e=>{const t=e.find(o=>o.url&&o.url.startsWith(r));t?chrome.tabs.reload(t.id):chrome.tabs.create({url:r})})}const Y=async()=>{B(),V(),Q(),_()};chrome.runtime.onMessage.addListener((r,e)=>{if(r.type==="close-tab"){const t=e.tab?.id;t&&chrome.tabs.remove(t)}});Y(); diff --git a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/content.js b/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/content.js index 0937bee..e6d5d02 100644 --- a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/content.js +++ b/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/content.js @@ -1,6 +1,6 @@ -function Ne(e,t){return function(){return e.apply(t,arguments)}}const{toString:rt}=Object.prototype,{getPrototypeOf:fe}=Object,{iterator:G,toStringTag:Be}=Symbol,Z=(e=>t=>{const n=rt.call(t);return e[n]||(e[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),P=e=>(e=e.toLowerCase(),t=>Z(t)===e),Q=e=>t=>typeof t===e,{isArray:j}=Array,M=Q("undefined");function ot(e){return e!==null&&!M(e)&&e.constructor!==null&&!M(e.constructor)&&x(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const Le=P("ArrayBuffer");function st(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&Le(e.buffer),t}const at=Q("string"),x=Q("function"),Fe=Q("number"),Y=e=>e!==null&&typeof e=="object",dt=e=>e===!0||e===!1,J=e=>{if(Z(e)!=="object")return!1;const t=fe(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Be in e)&&!(G in e)},ct=P("Date"),lt=P("File"),ut=P("Blob"),ft=P("FileList"),pt=e=>Y(e)&&x(e.pipe),vt=e=>{let t;return e&&(typeof FormData=="function"&&e instanceof FormData||x(e.append)&&((t=Z(e))==="formdata"||t==="object"&&x(e.toString)&&e.toString()==="[object FormData]"))},ht=P("URLSearchParams"),[mt,wt,yt,bt]=["ReadableStream","Request","Response","Headers"].map(P),Et=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function H(e,t,{allOwnKeys:n=!1}={}){if(e===null||typeof e>"u")return;let i,r;if(typeof e!="object"&&(e=[e]),j(e))for(i=0,r=e.length;i0;)if(r=n[i],t===r.toLowerCase())return r;return null}const F=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,De=e=>!M(e)&&e!==F;function se(){const{caseless:e}=De(this)&&this||{},t={},n=(i,r)=>{const o=e&&Ue(t,r)||r;J(t[o])&&J(i)?t[o]=se(t[o],i):J(i)?t[o]=se({},i):j(i)?t[o]=i.slice():t[o]=i};for(let i=0,r=arguments.length;i(H(t,(r,o)=>{n&&x(r)?e[o]=Ne(r,n):e[o]=r},{allOwnKeys:i}),e),Rt=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),St=(e,t,n,i)=>{e.prototype=Object.create(t.prototype,i),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},Tt=(e,t,n,i)=>{let r,o,s;const d={};if(t=t||{},e==null)return t;do{for(r=Object.getOwnPropertyNames(e),o=r.length;o-- >0;)s=r[o],(!i||i(s,e,t))&&!d[s]&&(t[s]=e[s],d[s]=!0);e=n!==!1&&fe(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},At=(e,t,n)=>{e=String(e),(n===void 0||n>e.length)&&(n=e.length),n-=t.length;const i=e.indexOf(t,n);return i!==-1&&i===n},Ot=e=>{if(!e)return null;if(j(e))return e;let t=e.length;if(!Fe(t))return null;const n=new Array(t);for(;t-- >0;)n[t]=e[t];return n},_t=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&fe(Uint8Array)),xt=(e,t)=>{const i=(e&&e[G]).call(e);let r;for(;(r=i.next())&&!r.done;){const o=r.value;t.call(e,o[0],o[1])}},Ct=(e,t)=>{let n;const i=[];for(;(n=e.exec(t))!==null;)i.push(n);return i},Pt=P("HTMLFormElement"),kt=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,i,r){return i.toUpperCase()+r}),we=(({hasOwnProperty:e})=>(t,n)=>e.call(t,n))(Object.prototype),Nt=P("RegExp"),je=(e,t)=>{const n=Object.getOwnPropertyDescriptors(e),i={};H(n,(r,o)=>{let s;(s=t(r,o,e))!==!1&&(i[o]=s||r)}),Object.defineProperties(e,i)},Bt=e=>{je(e,(t,n)=>{if(x(e)&&["arguments","caller","callee"].indexOf(n)!==-1)return!1;const i=e[n];if(x(i)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error("Can not rewrite read-only method '"+n+"'")})}})},Lt=(e,t)=>{const n={},i=r=>{r.forEach(o=>{n[o]=!0})};return j(e)?i(e):i(String(e).split(t)),n},Ft=()=>{},Ut=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t;function Dt(e){return!!(e&&x(e.append)&&e[Be]==="FormData"&&e[G])}const jt=e=>{const t=new Array(10),n=(i,r)=>{if(Y(i)){if(t.indexOf(i)>=0)return;if(!("toJSON"in i)){t[r]=i;const o=j(i)?[]:{};return H(i,(s,d)=>{const u=n(s,r+1);!M(u)&&(o[d]=u)}),t[r]=void 0,o}}return i};return n(e,0)},qt=P("AsyncFunction"),It=e=>e&&(Y(e)||x(e))&&x(e.then)&&x(e.catch),qe=((e,t)=>e?setImmediate:t?((n,i)=>(F.addEventListener("message",({source:r,data:o})=>{r===F&&o===n&&i.length&&i.shift()()},!1),r=>{i.push(r),F.postMessage(n,"*")}))(`axios@${Math.random()}`,[]):n=>setTimeout(n))(typeof setImmediate=="function",x(F.postMessage)),Mt=typeof queueMicrotask<"u"?queueMicrotask.bind(F):typeof process<"u"&&process.nextTick||qe,Ht=e=>e!=null&&x(e[G]),a={isArray:j,isArrayBuffer:Le,isBuffer:ot,isFormData:vt,isArrayBufferView:st,isString:at,isNumber:Fe,isBoolean:dt,isObject:Y,isPlainObject:J,isReadableStream:mt,isRequest:wt,isResponse:yt,isHeaders:bt,isUndefined:M,isDate:ct,isFile:lt,isBlob:ut,isRegExp:Nt,isFunction:x,isStream:pt,isURLSearchParams:ht,isTypedArray:_t,isFileList:ft,forEach:H,merge:se,extend:gt,trim:Et,stripBOM:Rt,inherits:St,toFlatObject:Tt,kindOf:Z,kindOfTest:P,endsWith:At,toArray:Ot,forEachEntry:xt,matchAll:Ct,isHTMLForm:Pt,hasOwnProperty:we,hasOwnProp:we,reduceDescriptors:je,freezeMethods:Bt,toObjectSet:Lt,toCamelCase:kt,noop:Ft,toFiniteNumber:Ut,findKey:Ue,global:F,isContextDefined:De,isSpecCompliantForm:Dt,toJSONObject:jt,isAsyncFn:qt,isThenable:It,setImmediate:qe,asap:Mt,isIterable:Ht};function h(e,t,n,i,r){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=e,this.name="AxiosError",t&&(this.code=t),n&&(this.config=n),i&&(this.request=i),r&&(this.response=r,this.status=r.status?r.status:null)}a.inherits(h,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:a.toJSONObject(this.config),code:this.code,status:this.status}}});const Ie=h.prototype,Me={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(e=>{Me[e]={value:e}});Object.defineProperties(h,Me);Object.defineProperty(Ie,"isAxiosError",{value:!0});h.from=(e,t,n,i,r,o)=>{const s=Object.create(Ie);return a.toFlatObject(e,s,function(u){return u!==Error.prototype},d=>d!=="isAxiosError"),h.call(s,e.message,t,n,i,r),s.cause=e,s.name=e.name,o&&Object.assign(s,o),s};const $t=null;function ae(e){return a.isPlainObject(e)||a.isArray(e)}function He(e){return a.endsWith(e,"[]")?e.slice(0,-2):e}function ye(e,t,n){return e?e.concat(t).map(function(r,o){return r=He(r),!n&&o?"["+r+"]":r}).join(n?".":""):t}function zt(e){return a.isArray(e)&&!e.some(ae)}const Jt=a.toFlatObject(a,{},null,function(t){return/^is[A-Z]/.test(t)});function ee(e,t,n){if(!a.isObject(e))throw new TypeError("target must be an object");t=t||new FormData,n=a.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(m,v){return!a.isUndefined(v[m])});const i=n.metaTokens,r=n.visitor||l,o=n.dots,s=n.indexes,u=(n.Blob||typeof Blob<"u"&&Blob)&&a.isSpecCompliantForm(t);if(!a.isFunction(r))throw new TypeError("visitor must be a function");function c(f){if(f===null)return"";if(a.isDate(f))return f.toISOString();if(a.isBoolean(f))return f.toString();if(!u&&a.isBlob(f))throw new h("Blob is not supported. Use a Buffer instead.");return a.isArrayBuffer(f)||a.isTypedArray(f)?u&&typeof Blob=="function"?new Blob([f]):Buffer.from(f):f}function l(f,m,v){let E=f;if(f&&!v&&typeof f=="object"){if(a.endsWith(m,"{}"))m=i?m:m.slice(0,-2),f=JSON.stringify(f);else if(a.isArray(f)&&zt(f)||(a.isFileList(f)||a.endsWith(m,"[]"))&&(E=a.toArray(f)))return m=He(m),E.forEach(function(T,N){!(a.isUndefined(T)||T===null)&&t.append(s===!0?ye([m],N,o):s===null?m:m+"[]",c(T))}),!1}return ae(f)?!0:(t.append(ye(v,m,o),c(f)),!1)}const p=[],y=Object.assign(Jt,{defaultVisitor:l,convertValue:c,isVisitable:ae});function R(f,m){if(!a.isUndefined(f)){if(p.indexOf(f)!==-1)throw Error("Circular reference detected in "+m.join("."));p.push(f),a.forEach(f,function(E,S){(!(a.isUndefined(E)||E===null)&&r.call(t,E,a.isString(S)?S.trim():S,m,y))===!0&&R(E,m?m.concat(S):[S])}),p.pop()}}if(!a.isObject(e))throw new TypeError("data must be an object");return R(e),t}function be(e){const t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,function(i){return t[i]})}function pe(e,t){this._pairs=[],e&&ee(e,this,t)}const $e=pe.prototype;$e.append=function(t,n){this._pairs.push([t,n])};$e.toString=function(t){const n=t?function(i){return t.call(this,i,be)}:be;return this._pairs.map(function(r){return n(r[0])+"="+n(r[1])},"").join("&")};function Vt(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function ze(e,t,n){if(!t)return e;const i=n&&n.encode||Vt;a.isFunction(n)&&(n={serialize:n});const r=n&&n.serialize;let o;if(r?o=r(t,n):o=a.isURLSearchParams(t)?t.toString():new pe(t,n).toString(i),o){const s=e.indexOf("#");s!==-1&&(e=e.slice(0,s)),e+=(e.indexOf("?")===-1?"?":"&")+o}return e}class Ee{constructor(){this.handlers=[]}use(t,n,i){return this.handlers.push({fulfilled:t,rejected:n,synchronous:i?i.synchronous:!1,runWhen:i?i.runWhen:null}),this.handlers.length-1}eject(t){this.handlers[t]&&(this.handlers[t]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(t){a.forEach(this.handlers,function(i){i!==null&&t(i)})}}const Je={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},Xt=typeof URLSearchParams<"u"?URLSearchParams:pe,Kt=typeof FormData<"u"?FormData:null,Wt=typeof Blob<"u"?Blob:null,Gt={isBrowser:!0,classes:{URLSearchParams:Xt,FormData:Kt,Blob:Wt},protocols:["http","https","file","blob","url","data"]},ve=typeof window<"u"&&typeof document<"u",de=typeof navigator=="object"&&navigator||void 0,Zt=ve&&(!de||["ReactNative","NativeScript","NS"].indexOf(de.product)<0),Qt=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",Yt=ve&&window.location.href||"http://localhost",en=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:ve,hasStandardBrowserEnv:Zt,hasStandardBrowserWebWorkerEnv:Qt,navigator:de,origin:Yt},Symbol.toStringTag,{value:"Module"})),O={...en,...Gt};function tn(e,t){return ee(e,new O.classes.URLSearchParams,Object.assign({visitor:function(n,i,r,o){return O.isNode&&a.isBuffer(n)?(this.append(i,n.toString("base64")),!1):o.defaultVisitor.apply(this,arguments)}},t))}function nn(e){return a.matchAll(/\w+|\[(\w*)]/g,e).map(t=>t[0]==="[]"?"":t[1]||t[0])}function rn(e){const t={},n=Object.keys(e);let i;const r=n.length;let o;for(i=0;i=n.length;return s=!s&&a.isArray(r)?r.length:s,u?(a.hasOwnProp(r,s)?r[s]=[r[s],i]:r[s]=i,!d):((!r[s]||!a.isObject(r[s]))&&(r[s]=[]),t(n,i,r[s],o)&&a.isArray(r[s])&&(r[s]=rn(r[s])),!d)}if(a.isFormData(e)&&a.isFunction(e.entries)){const n={};return a.forEachEntry(e,(i,r)=>{t(nn(i),r,n,0)}),n}return null}function on(e,t,n){if(a.isString(e))try{return(t||JSON.parse)(e),a.trim(e)}catch(i){if(i.name!=="SyntaxError")throw i}return(n||JSON.stringify)(e)}const $={transitional:Je,adapter:["xhr","http","fetch"],transformRequest:[function(t,n){const i=n.getContentType()||"",r=i.indexOf("application/json")>-1,o=a.isObject(t);if(o&&a.isHTMLForm(t)&&(t=new FormData(t)),a.isFormData(t))return r?JSON.stringify(Ve(t)):t;if(a.isArrayBuffer(t)||a.isBuffer(t)||a.isStream(t)||a.isFile(t)||a.isBlob(t)||a.isReadableStream(t))return t;if(a.isArrayBufferView(t))return t.buffer;if(a.isURLSearchParams(t))return n.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),t.toString();let d;if(o){if(i.indexOf("application/x-www-form-urlencoded")>-1)return tn(t,this.formSerializer).toString();if((d=a.isFileList(t))||i.indexOf("multipart/form-data")>-1){const u=this.env&&this.env.FormData;return ee(d?{"files[]":t}:t,u&&new u,this.formSerializer)}}return o||r?(n.setContentType("application/json",!1),on(t)):t}],transformResponse:[function(t){const n=this.transitional||$.transitional,i=n&&n.forcedJSONParsing,r=this.responseType==="json";if(a.isResponse(t)||a.isReadableStream(t))return t;if(t&&a.isString(t)&&(i&&!this.responseType||r)){const s=!(n&&n.silentJSONParsing)&&r;try{return JSON.parse(t)}catch(d){if(s)throw d.name==="SyntaxError"?h.from(d,h.ERR_BAD_RESPONSE,this,null,this.response):d}}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:O.classes.FormData,Blob:O.classes.Blob},validateStatus:function(t){return t>=200&&t<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};a.forEach(["delete","get","head","post","put","patch"],e=>{$.headers[e]={}});const sn=a.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),an=e=>{const t={};let n,i,r;return e&&e.split(` -`).forEach(function(s){r=s.indexOf(":"),n=s.substring(0,r).trim().toLowerCase(),i=s.substring(r+1).trim(),!(!n||t[n]&&sn[n])&&(n==="set-cookie"?t[n]?t[n].push(i):t[n]=[i]:t[n]=t[n]?t[n]+", "+i:i)}),t},ge=Symbol("internals");function I(e){return e&&String(e).trim().toLowerCase()}function V(e){return e===!1||e==null?e:a.isArray(e)?e.map(V):String(e)}function dn(e){const t=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let i;for(;i=n.exec(e);)t[i[1]]=i[2];return t}const cn=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function ie(e,t,n,i,r){if(a.isFunction(i))return i.call(this,t,n);if(r&&(t=n),!!a.isString(t)){if(a.isString(i))return t.indexOf(i)!==-1;if(a.isRegExp(i))return i.test(t)}}function ln(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,n,i)=>n.toUpperCase()+i)}function un(e,t){const n=a.toCamelCase(" "+t);["get","set","has"].forEach(i=>{Object.defineProperty(e,i+n,{value:function(r,o,s){return this[i].call(this,t,r,o,s)},configurable:!0})})}let C=class{constructor(t){t&&this.set(t)}set(t,n,i){const r=this;function o(d,u,c){const l=I(u);if(!l)throw new Error("header name must be a non-empty string");const p=a.findKey(r,l);(!p||r[p]===void 0||c===!0||c===void 0&&r[p]!==!1)&&(r[p||u]=V(d))}const s=(d,u)=>a.forEach(d,(c,l)=>o(c,l,u));if(a.isPlainObject(t)||t instanceof this.constructor)s(t,n);else if(a.isString(t)&&(t=t.trim())&&!cn(t))s(an(t),n);else if(a.isObject(t)&&a.isIterable(t)){let d={},u,c;for(const l of t){if(!a.isArray(l))throw TypeError("Object iterator must return a key-value pair");d[c=l[0]]=(u=d[c])?a.isArray(u)?[...u,l[1]]:[u,l[1]]:l[1]}s(d,n)}else t!=null&&o(n,t,i);return this}get(t,n){if(t=I(t),t){const i=a.findKey(this,t);if(i){const r=this[i];if(!n)return r;if(n===!0)return dn(r);if(a.isFunction(n))return n.call(this,r,i);if(a.isRegExp(n))return n.exec(r);throw new TypeError("parser must be boolean|regexp|function")}}}has(t,n){if(t=I(t),t){const i=a.findKey(this,t);return!!(i&&this[i]!==void 0&&(!n||ie(this,this[i],i,n)))}return!1}delete(t,n){const i=this;let r=!1;function o(s){if(s=I(s),s){const d=a.findKey(i,s);d&&(!n||ie(i,i[d],d,n))&&(delete i[d],r=!0)}}return a.isArray(t)?t.forEach(o):o(t),r}clear(t){const n=Object.keys(this);let i=n.length,r=!1;for(;i--;){const o=n[i];(!t||ie(this,this[o],o,t,!0))&&(delete this[o],r=!0)}return r}normalize(t){const n=this,i={};return a.forEach(this,(r,o)=>{const s=a.findKey(i,o);if(s){n[s]=V(r),delete n[o];return}const d=t?ln(o):String(o).trim();d!==o&&delete n[o],n[d]=V(r),i[d]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){const n=Object.create(null);return a.forEach(this,(i,r)=>{i!=null&&i!==!1&&(n[r]=t&&a.isArray(i)?i.join(", "):i)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,n])=>t+": "+n).join(` -`)}getSetCookie(){return this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...n){const i=new this(t);return n.forEach(r=>i.set(r)),i}static accessor(t){const i=(this[ge]=this[ge]={accessors:{}}).accessors,r=this.prototype;function o(s){const d=I(s);i[d]||(un(r,s),i[d]=!0)}return a.isArray(t)?t.forEach(o):o(t),this}};C.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);a.reduceDescriptors(C.prototype,({value:e},t)=>{let n=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(i){this[n]=i}}});a.freezeMethods(C);function re(e,t){const n=this||$,i=t||n,r=C.from(i.headers);let o=i.data;return a.forEach(e,function(d){o=d.call(n,o,r.normalize(),t?t.status:void 0)}),r.normalize(),o}function Xe(e){return!!(e&&e.__CANCEL__)}function q(e,t,n){h.call(this,e??"canceled",h.ERR_CANCELED,t,n),this.name="CanceledError"}a.inherits(q,h,{__CANCEL__:!0});function Ke(e,t,n){const i=n.config.validateStatus;!n.status||!i||i(n.status)?e(n):t(new h("Request failed with status code "+n.status,[h.ERR_BAD_REQUEST,h.ERR_BAD_RESPONSE][Math.floor(n.status/100)-4],n.config,n.request,n))}function fn(e){const t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}function pn(e,t){e=e||10;const n=new Array(e),i=new Array(e);let r=0,o=0,s;return t=t!==void 0?t:1e3,function(u){const c=Date.now(),l=i[o];s||(s=c),n[r]=u,i[r]=c;let p=o,y=0;for(;p!==r;)y+=n[p++],p=p%e;if(r=(r+1)%e,r===o&&(o=(o+1)%e),c-s{n=l,r=null,o&&(clearTimeout(o),o=null),e.apply(null,c)};return[(...c)=>{const l=Date.now(),p=l-n;p>=i?s(c,l):(r=c,o||(o=setTimeout(()=>{o=null,s(r)},i-p)))},()=>r&&s(r)]}const K=(e,t,n=3)=>{let i=0;const r=pn(50,250);return vn(o=>{const s=o.loaded,d=o.lengthComputable?o.total:void 0,u=s-i,c=r(u),l=s<=d;i=s;const p={loaded:s,total:d,progress:d?s/d:void 0,bytes:u,rate:c||void 0,estimated:c&&d&&l?(d-s)/c:void 0,event:o,lengthComputable:d!=null,[t?"download":"upload"]:!0};e(p)},n)},Re=(e,t)=>{const n=e!=null;return[i=>t[0]({lengthComputable:n,total:e,loaded:i}),t[1]]},Se=e=>(...t)=>a.asap(()=>e(...t)),hn=O.hasStandardBrowserEnv?((e,t)=>n=>(n=new URL(n,O.origin),e.protocol===n.protocol&&e.host===n.host&&(t||e.port===n.port)))(new URL(O.origin),O.navigator&&/(msie|trident)/i.test(O.navigator.userAgent)):()=>!0,mn=O.hasStandardBrowserEnv?{write(e,t,n,i,r,o){const s=[e+"="+encodeURIComponent(t)];a.isNumber(n)&&s.push("expires="+new Date(n).toGMTString()),a.isString(i)&&s.push("path="+i),a.isString(r)&&s.push("domain="+r),o===!0&&s.push("secure"),document.cookie=s.join("; ")},read(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove(e){this.write(e,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function wn(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}function yn(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""):e}function We(e,t,n){let i=!wn(t);return e&&(i||n==!1)?yn(e,t):t}const Te=e=>e instanceof C?{...e}:e;function D(e,t){t=t||{};const n={};function i(c,l,p,y){return a.isPlainObject(c)&&a.isPlainObject(l)?a.merge.call({caseless:y},c,l):a.isPlainObject(l)?a.merge({},l):a.isArray(l)?l.slice():l}function r(c,l,p,y){if(a.isUndefined(l)){if(!a.isUndefined(c))return i(void 0,c,p,y)}else return i(c,l,p,y)}function o(c,l){if(!a.isUndefined(l))return i(void 0,l)}function s(c,l){if(a.isUndefined(l)){if(!a.isUndefined(c))return i(void 0,c)}else return i(void 0,l)}function d(c,l,p){if(p in t)return i(c,l);if(p in e)return i(void 0,c)}const u={url:o,method:o,data:o,baseURL:s,transformRequest:s,transformResponse:s,paramsSerializer:s,timeout:s,timeoutMessage:s,withCredentials:s,withXSRFToken:s,adapter:s,responseType:s,xsrfCookieName:s,xsrfHeaderName:s,onUploadProgress:s,onDownloadProgress:s,decompress:s,maxContentLength:s,maxBodyLength:s,beforeRedirect:s,transport:s,httpAgent:s,httpsAgent:s,cancelToken:s,socketPath:s,responseEncoding:s,validateStatus:d,headers:(c,l,p)=>r(Te(c),Te(l),p,!0)};return a.forEach(Object.keys(Object.assign({},e,t)),function(l){const p=u[l]||r,y=p(e[l],t[l],l);a.isUndefined(y)&&p!==d||(n[l]=y)}),n}const Ge=e=>{const t=D({},e);let{data:n,withXSRFToken:i,xsrfHeaderName:r,xsrfCookieName:o,headers:s,auth:d}=t;t.headers=s=C.from(s),t.url=ze(We(t.baseURL,t.url,t.allowAbsoluteUrls),e.params,e.paramsSerializer),d&&s.set("Authorization","Basic "+btoa((d.username||"")+":"+(d.password?unescape(encodeURIComponent(d.password)):"")));let u;if(a.isFormData(n)){if(O.hasStandardBrowserEnv||O.hasStandardBrowserWebWorkerEnv)s.setContentType(void 0);else if((u=s.getContentType())!==!1){const[c,...l]=u?u.split(";").map(p=>p.trim()).filter(Boolean):[];s.setContentType([c||"multipart/form-data",...l].join("; "))}}if(O.hasStandardBrowserEnv&&(i&&a.isFunction(i)&&(i=i(t)),i||i!==!1&&hn(t.url))){const c=r&&o&&mn.read(o);c&&s.set(r,c)}return t},bn=typeof XMLHttpRequest<"u",En=bn&&function(e){return new Promise(function(n,i){const r=Ge(e);let o=r.data;const s=C.from(r.headers).normalize();let{responseType:d,onUploadProgress:u,onDownloadProgress:c}=r,l,p,y,R,f;function m(){R&&R(),f&&f(),r.cancelToken&&r.cancelToken.unsubscribe(l),r.signal&&r.signal.removeEventListener("abort",l)}let v=new XMLHttpRequest;v.open(r.method.toUpperCase(),r.url,!0),v.timeout=r.timeout;function E(){if(!v)return;const T=C.from("getAllResponseHeaders"in v&&v.getAllResponseHeaders()),_={data:!d||d==="text"||d==="json"?v.responseText:v.response,status:v.status,statusText:v.statusText,headers:T,config:e,request:v};Ke(function(L){n(L),m()},function(L){i(L),m()},_),v=null}"onloadend"in v?v.onloadend=E:v.onreadystatechange=function(){!v||v.readyState!==4||v.status===0&&!(v.responseURL&&v.responseURL.indexOf("file:")===0)||setTimeout(E)},v.onabort=function(){v&&(i(new h("Request aborted",h.ECONNABORTED,e,v)),v=null)},v.onerror=function(){i(new h("Network Error",h.ERR_NETWORK,e,v)),v=null},v.ontimeout=function(){let N=r.timeout?"timeout of "+r.timeout+"ms exceeded":"timeout exceeded";const _=r.transitional||Je;r.timeoutErrorMessage&&(N=r.timeoutErrorMessage),i(new h(N,_.clarifyTimeoutError?h.ETIMEDOUT:h.ECONNABORTED,e,v)),v=null},o===void 0&&s.setContentType(null),"setRequestHeader"in v&&a.forEach(s.toJSON(),function(N,_){v.setRequestHeader(_,N)}),a.isUndefined(r.withCredentials)||(v.withCredentials=!!r.withCredentials),d&&d!=="json"&&(v.responseType=r.responseType),c&&([y,f]=K(c,!0),v.addEventListener("progress",y)),u&&v.upload&&([p,R]=K(u),v.upload.addEventListener("progress",p),v.upload.addEventListener("loadend",R)),(r.cancelToken||r.signal)&&(l=T=>{v&&(i(!T||T.type?new q(null,e,v):T),v.abort(),v=null)},r.cancelToken&&r.cancelToken.subscribe(l),r.signal&&(r.signal.aborted?l():r.signal.addEventListener("abort",l)));const S=fn(r.url);if(S&&O.protocols.indexOf(S)===-1){i(new h("Unsupported protocol "+S+":",h.ERR_BAD_REQUEST,e));return}v.send(o||null)})},gn=(e,t)=>{const{length:n}=e=e?e.filter(Boolean):[];if(t||n){let i=new AbortController,r;const o=function(c){if(!r){r=!0,d();const l=c instanceof Error?c:this.reason;i.abort(l instanceof h?l:new q(l instanceof Error?l.message:l))}};let s=t&&setTimeout(()=>{s=null,o(new h(`timeout ${t} of ms exceeded`,h.ETIMEDOUT))},t);const d=()=>{e&&(s&&clearTimeout(s),s=null,e.forEach(c=>{c.unsubscribe?c.unsubscribe(o):c.removeEventListener("abort",o)}),e=null)};e.forEach(c=>c.addEventListener("abort",o));const{signal:u}=i;return u.unsubscribe=()=>a.asap(d),u}},Rn=function*(e,t){let n=e.byteLength;if(n{const r=Sn(e,t);let o=0,s,d=u=>{s||(s=!0,i&&i(u))};return new ReadableStream({async pull(u){try{const{done:c,value:l}=await r.next();if(c){d(),u.close();return}let p=l.byteLength;if(n){let y=o+=p;n(y)}u.enqueue(new Uint8Array(l))}catch(c){throw d(c),c}},cancel(u){return d(u),r.return()}},{highWaterMark:2})},te=typeof fetch=="function"&&typeof Request=="function"&&typeof Response=="function",Ze=te&&typeof ReadableStream=="function",An=te&&(typeof TextEncoder=="function"?(e=>t=>e.encode(t))(new TextEncoder):async e=>new Uint8Array(await new Response(e).arrayBuffer())),Qe=(e,...t)=>{try{return!!e(...t)}catch{return!1}},On=Ze&&Qe(()=>{let e=!1;const t=new Request(O.origin,{body:new ReadableStream,method:"POST",get duplex(){return e=!0,"half"}}).headers.has("Content-Type");return e&&!t}),Oe=64*1024,ce=Ze&&Qe(()=>a.isReadableStream(new Response("").body)),W={stream:ce&&(e=>e.body)};te&&(e=>{["text","arrayBuffer","blob","formData","stream"].forEach(t=>{!W[t]&&(W[t]=a.isFunction(e[t])?n=>n[t]():(n,i)=>{throw new h(`Response type '${t}' is not supported`,h.ERR_NOT_SUPPORT,i)})})})(new Response);const _n=async e=>{if(e==null)return 0;if(a.isBlob(e))return e.size;if(a.isSpecCompliantForm(e))return(await new Request(O.origin,{method:"POST",body:e}).arrayBuffer()).byteLength;if(a.isArrayBufferView(e)||a.isArrayBuffer(e))return e.byteLength;if(a.isURLSearchParams(e)&&(e=e+""),a.isString(e))return(await An(e)).byteLength},xn=async(e,t)=>{const n=a.toFiniteNumber(e.getContentLength());return n??_n(t)},Cn=te&&(async e=>{let{url:t,method:n,data:i,signal:r,cancelToken:o,timeout:s,onDownloadProgress:d,onUploadProgress:u,responseType:c,headers:l,withCredentials:p="same-origin",fetchOptions:y}=Ge(e);c=c?(c+"").toLowerCase():"text";let R=gn([r,o&&o.toAbortSignal()],s),f;const m=R&&R.unsubscribe&&(()=>{R.unsubscribe()});let v;try{if(u&&On&&n!=="get"&&n!=="head"&&(v=await xn(l,i))!==0){let _=new Request(t,{method:"POST",body:i,duplex:"half"}),B;if(a.isFormData(i)&&(B=_.headers.get("content-type"))&&l.setContentType(B),_.body){const[L,z]=Re(v,K(Se(u)));i=Ae(_.body,Oe,L,z)}}a.isString(p)||(p=p?"include":"omit");const E="credentials"in Request.prototype;f=new Request(t,{...y,signal:R,method:n.toUpperCase(),headers:l.normalize().toJSON(),body:i,duplex:"half",credentials:E?p:void 0});let S=await fetch(f,y);const T=ce&&(c==="stream"||c==="response");if(ce&&(d||T&&m)){const _={};["status","statusText","headers"].forEach(me=>{_[me]=S[me]});const B=a.toFiniteNumber(S.headers.get("content-length")),[L,z]=d&&Re(B,K(Se(d),!0))||[];S=new Response(Ae(S.body,Oe,L,()=>{z&&z(),m&&m()}),_)}c=c||"text";let N=await W[a.findKey(W,c)||"text"](S,e);return!T&&m&&m(),await new Promise((_,B)=>{Ke(_,B,{data:N,headers:C.from(S.headers),status:S.status,statusText:S.statusText,config:e,request:f})})}catch(E){throw m&&m(),E&&E.name==="TypeError"&&/Load failed|fetch/i.test(E.message)?Object.assign(new h("Network Error",h.ERR_NETWORK,e,f),{cause:E.cause||E}):h.from(E,E&&E.code,e,f)}}),le={http:$t,xhr:En,fetch:Cn};a.forEach(le,(e,t)=>{if(e){try{Object.defineProperty(e,"name",{value:t})}catch{}Object.defineProperty(e,"adapterName",{value:t})}});const _e=e=>`- ${e}`,Pn=e=>a.isFunction(e)||e===null||e===!1,Ye={getAdapter:e=>{e=a.isArray(e)?e:[e];const{length:t}=e;let n,i;const r={};for(let o=0;o`adapter ${d} `+(u===!1?"is not supported by the environment":"is not available in the build"));let s=t?o.length>1?`since : -`+o.map(_e).join(` -`):" "+_e(o[0]):"as no adapter specified";throw new h("There is no suitable adapter to dispatch the request "+s,"ERR_NOT_SUPPORT")}return i},adapters:le};function oe(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new q(null,e)}function xe(e){return oe(e),e.headers=C.from(e.headers),e.data=re.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),Ye.getAdapter(e.adapter||$.adapter)(e).then(function(i){return oe(e),i.data=re.call(e,e.transformResponse,i),i.headers=C.from(i.headers),i},function(i){return Xe(i)||(oe(e),i&&i.response&&(i.response.data=re.call(e,e.transformResponse,i.response),i.response.headers=C.from(i.response.headers))),Promise.reject(i)})}const et="1.10.0",ne={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{ne[e]=function(i){return typeof i===e||"a"+(t<1?"n ":" ")+e}});const Ce={};ne.transitional=function(t,n,i){function r(o,s){return"[Axios v"+et+"] Transitional option '"+o+"'"+s+(i?". "+i:"")}return(o,s,d)=>{if(t===!1)throw new h(r(s," has been removed"+(n?" in "+n:"")),h.ERR_DEPRECATED);return n&&!Ce[s]&&(Ce[s]=!0,console.warn(r(s," has been deprecated since v"+n+" and will be removed in the near future"))),t?t(o,s,d):!0}};ne.spelling=function(t){return(n,i)=>(console.warn(`${i} is likely a misspelling of ${t}`),!0)};function kn(e,t,n){if(typeof e!="object")throw new h("options must be an object",h.ERR_BAD_OPTION_VALUE);const i=Object.keys(e);let r=i.length;for(;r-- >0;){const o=i[r],s=t[o];if(s){const d=e[o],u=d===void 0||s(d,o,e);if(u!==!0)throw new h("option "+o+" must be "+u,h.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new h("Unknown option "+o,h.ERR_BAD_OPTION)}}const X={assertOptions:kn,validators:ne},k=X.validators;let U=class{constructor(t){this.defaults=t||{},this.interceptors={request:new Ee,response:new Ee}}async request(t,n){try{return await this._request(t,n)}catch(i){if(i instanceof Error){let r={};Error.captureStackTrace?Error.captureStackTrace(r):r=new Error;const o=r.stack?r.stack.replace(/^.+\n/,""):"";try{i.stack?o&&!String(i.stack).endsWith(o.replace(/^.+\n.+\n/,""))&&(i.stack+=` -`+o):i.stack=o}catch{}}throw i}}_request(t,n){typeof t=="string"?(n=n||{},n.url=t):n=t||{},n=D(this.defaults,n);const{transitional:i,paramsSerializer:r,headers:o}=n;i!==void 0&&X.assertOptions(i,{silentJSONParsing:k.transitional(k.boolean),forcedJSONParsing:k.transitional(k.boolean),clarifyTimeoutError:k.transitional(k.boolean)},!1),r!=null&&(a.isFunction(r)?n.paramsSerializer={serialize:r}:X.assertOptions(r,{encode:k.function,serialize:k.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),X.assertOptions(n,{baseUrl:k.spelling("baseURL"),withXsrfToken:k.spelling("withXSRFToken")},!0),n.method=(n.method||this.defaults.method||"get").toLowerCase();let s=o&&a.merge(o.common,o[n.method]);o&&a.forEach(["delete","get","head","post","put","patch","common"],f=>{delete o[f]}),n.headers=C.concat(s,o);const d=[];let u=!0;this.interceptors.request.forEach(function(m){typeof m.runWhen=="function"&&m.runWhen(n)===!1||(u=u&&m.synchronous,d.unshift(m.fulfilled,m.rejected))});const c=[];this.interceptors.response.forEach(function(m){c.push(m.fulfilled,m.rejected)});let l,p=0,y;if(!u){const f=[xe.bind(this),void 0];for(f.unshift.apply(f,d),f.push.apply(f,c),y=f.length,l=Promise.resolve(n);p{if(!i._listeners)return;let o=i._listeners.length;for(;o-- >0;)i._listeners[o](r);i._listeners=null}),this.promise.then=r=>{let o;const s=new Promise(d=>{i.subscribe(d),o=d}).then(r);return s.cancel=function(){i.unsubscribe(o)},s},t(function(o,s,d){i.reason||(i.reason=new q(o,s,d),n(i.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const n=this._listeners.indexOf(t);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const t=new AbortController,n=i=>{t.abort(i)};return this.subscribe(n),t.signal.unsubscribe=()=>this.unsubscribe(n),t.signal}static source(){let t;return{token:new tt(function(r){t=r}),cancel:t}}};function Bn(e){return function(n){return e.apply(null,n)}}function Ln(e){return a.isObject(e)&&e.isAxiosError===!0}const ue={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(ue).forEach(([e,t])=>{ue[t]=e});function nt(e){const t=new U(e),n=Ne(U.prototype.request,t);return a.extend(n,U.prototype,t,{allOwnKeys:!0}),a.extend(n,t,null,{allOwnKeys:!0}),n.create=function(r){return nt(D(e,r))},n}const g=nt($);g.Axios=U;g.CanceledError=q;g.CancelToken=Nn;g.isCancel=Xe;g.VERSION=et;g.toFormData=ee;g.AxiosError=h;g.Cancel=g.CanceledError;g.all=function(t){return Promise.all(t)};g.spread=Bn;g.isAxiosError=Ln;g.mergeConfig=D;g.AxiosHeaders=C;g.formToJSON=e=>Ve(a.isHTMLForm(e)?new FormData(e):e);g.getAdapter=Ye.getAdapter;g.HttpStatusCode=ue;g.default=g;const{Axios:Yn,AxiosError:ei,CanceledError:ti,isCancel:ni,CancelToken:ii,VERSION:ri,all:oi,Cancel:si,isAxiosError:ai,spread:di,toFormData:ci,AxiosHeaders:li,HttpStatusCode:ui,formToJSON:fi,getAdapter:pi,mergeConfig:vi}=g,he=g.create({baseURL:"http://localhost:4000/api/v1",headers:{"Content-Type":"application/json"}});class Fn{async sync(t){try{const{data:n}=await he.post("/products/sync",{items:t});return console.log("[NestJS] Response (bulk):",n),n}catch(n){throw console.error("[NestJS] Error (bulk):",n),n}}}const Un=new Fn;function b(e){return new Promise(t=>setTimeout(t,e))}class Dn{base64ToFile(t,n,i){const r=t.includes(",")?t.split(",")[1]:t,o=atob(r),s=new ArrayBuffer(o.length),d=new Uint8Array(s);for(let c=0;c{let s=0,d=!1;const u=()=>{const c=document.evaluate(d&&r?r:t,document,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;if(c instanceof HTMLElement){o(c);return}s++,s{const s=new FileReader;s.onloadend=()=>{typeof s.result=="string"?r(s.result.split(",")[1]):o("Không thể đọc dữ liệu ảnh")},s.onerror=o,s.readAsDataURL(i)})}getImageExtension(t){try{const i=new URL(t).pathname.match(/\.([a-zA-Z0-9]+)$/);return i?i[1].toLowerCase():null}catch{const r=t.split("?")[0].match(/\.([a-zA-Z0-9]+)$/);return r?r[1].toLowerCase():null}}imageLocalToBase64(t){return new Promise((n,i)=>{try{const r=chrome.runtime.getURL(`${t}`);fetch(r).then(o=>o.blob()).then(o=>{const s=new FileReader;s.onloadend=()=>n(s.result),s.onerror=i,s.readAsDataURL(o)}).catch(i)}catch(r){i(r)}})}scrollToElement(t,n="smooth"){t&&t.scrollIntoView({behavior:n,block:"center",inline:"nearest"})}getElementPointCoores(t){if(!t)return null;const n=t.getBoundingClientRect(),i=n.left+n.width/2,r=n.top+n.height/2;return{x:i,y:r}}setInputValue(t,n){t&&(t.value=n,t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0})))}writeToInput=async(t,n,i)=>{const r=await this.getElementByXPath(n,{xpathFallback:i});if(!r)throw new Error("Xpath is not found with value: "+t);this.scrollToElement(r),this.clickByPoint(r),this.setInputValue(r,t)};pressEnter(t){if(!t)throw new Error("Textarea not found:",t);t.focus(),["keydown","keypress","keyup"].forEach(n=>{t.dispatchEvent(new KeyboardEvent(n,{key:"Enter",code:"Enter",keyCode:13,which:13,bubbles:!0,cancelable:!0}))})}}const w=new Dn,A={file__image_input:'input[type="file"]',title_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div[2]/div/div/div[5]/div/div/div/label/div/input",price_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[6]/div/div/div/label/div/input",brand_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[2]/div/div/div/label/div/input",brand_input_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[2]/div/div/div/label/div/input",description_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[3]/div/div/div/label/div/div/textarea",description_input_falback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[3]/div/div/div/label/div/div/textarea",sku_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[6]/div/div/div[1]/label/div/input",sku_input_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[6]/div/div/div[1]/label/div/input",category_select:{wraper:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[7]/div/div/div/div",container:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/div/div/div/div/span/div"},condition_select:{wraper:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[8]/div/div/div/div",container:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/div/div/div/div[1]/div"},tags_input:{input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea",input_falback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea"},location_select:{input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input",input_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input",wraper:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div",container:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul",container_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul"},next_btn:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[5]/div/div/div",publish_btn:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[4]/div[2]/div/div",products:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[2]/div/div/div[2]/div[1]/div/div[2]/div/div/span/div/div",products_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[2]/div/div/div[2]/div[1]/div/div[2]/div[2]/div"},jn=async e=>{const t=new DataTransfer;for(const i of e.images){const r=await w.imageUrlToBase64(i);console.log("Base64:",i.slice(0,50)+"...");const o=w.base64ToFile(r,e.sku,w.getImageExtension(i)||"jpg");t.items.add(o)}const n=document.querySelector(A.file__image_input);n?(n.files=t.files,n.dispatchEvent(new Event("change",{bubbles:!0}))):console.error("Không tìm thấy input[type='file']")},Pe=async(e,t)=>{const n=await w.getElementByXPath(t.wraper);if(!n)throw new Error("Wrapper xpath not found");w.scrollToElement(n),w.clickByPoint(n),await b(200);const i=await w.getElementByXPath(t.container);if(!i)throw new Error("Container xpath not found");const r=Array.from(i.children).find(o=>o.textContent?.trim().toLocaleLowerCase().replace(/–/g,"-").includes(e.toLocaleLowerCase()));if(!r)throw new Error(`No child found with text "${e}"`);w.scrollToElement(r),await b(200),w.clickByPoint(r)},qn=async(e,{input:t,...n})=>{await w.writeToInput(e,t,n.input_fallback),await b(200);const i=await w.getElementByXPath(n.container,{xpathFallback:n.container_fallback});if(!i)throw new Error("Container xpath not found");w.scrollToElement(i);const r=Array.from(i.children).find(o=>o.textContent?.trim().toLocaleLowerCase().includes(e.toLocaleLowerCase()));if(!r)throw new Error(`No child found with text "${e}"`);w.scrollToElement(r),await b(200),w.clickByPoint(r)},In=async(e,t)=>{const n=await w.getElementByXPath(t.input,{xpathFallback:t?.input_falback});if(!n)throw new Error("Input is not found");w.scrollToElement(n),await b(200);for(const i of e)await w.writeToInput(i,t.input,t?.input_falback),await b(200),w.pressEnter(n)},ke=async(e,t)=>{const{data:n}=await he({url:"products/publist-finish/"+e.id,method:"POST",data:t});return n},Mn=async(e,t)=>{const{data:n}=await he({url:"products/delete-finish/"+e.id,method:"POST",data:t});return n},Hn=async()=>{const e=await w.getElementByXPath(A.next_btn);if(!e)throw new Error("Next button is not found");w.clickByPoint(e)},$n=async()=>{const e=await w.getElementByXPath(A.publish_btn);if(!e)throw new Error("Publist button is not found");w.clickByPoint(e)},zn=async e=>(console.log({item:e}),await b(1e3),await jn(e),await b(200),w.writeToInput(e.title,A.title_input),await b(200),w.writeToInput(String(e.price),A.price_input),await b(200),await Pe(e.category,A.category_select),await b(200),await Pe(e.condition,A.condition_select),e.brand&&(await b(200),await w.writeToInput(e.brand,A.brand_input,A.brand_input_fallback)),await b(200),await w.writeToInput(e.description,A.description_input,A.description_input_falback),await b(200),await In(e.tags,A.tags_input),await b(200),await w.writeToInput(e.sku,A.sku_input,A.sku_input_fallback),e?.location&&(await b(200),await qn(e.location,A.location_select)),await b(200),await Hn(),await b(200),await $n(),!0),it=async()=>{const e=await w.getElementByXPath(A.products),t=await w.getElementByXPath(A.products_fallback),n=[e,t].filter(Boolean);return n.length===0?[]:n.flatMap(i=>Jn(i))};function Jn(e){return Array.from(e.children).map(n=>{const r=n.querySelector('span[dir="auto"], div[dir="auto"]')?.textContent?.trim()||"",s=Array.from(n.querySelectorAll('span[dir="auto"]')).find(u=>/\d/.test(u.textContent||"")&&/[AU$]/.test(u.textContent||""))?.textContent?.match(/[\d,]+(?:\.\d+)?/),d=s?parseFloat(s[0].replace(/,/g,"")):0;return{title:r,price:d,el:e}})}const Vn=async()=>{if(!window.location.href.includes("https://www.facebook.com/marketplace/you/selling"))return;const t=await it();if(!t.length)return;const n=await Un.sync(t.map(i=>({title:i.title,price:i.price})));console.log({response:n})},Xn=async e=>{chrome.runtime.sendMessage({type:"close-tab",payload:e})},Kn=async e=>{const t=await it(),n=t.find(c=>c.title==e.title&&c.price==e.price);if(console.log({payload:e,product:n,products:t}),!n)return;const r=n.el.querySelector(`[aria-label="More options for ${n.title}"]`);if(console.log({optionEl:r}),!r)return;r.click?.(),await b(2e3);const o=Array.from(document.querySelectorAll('[role="menuitem"]'));console.log({items:o}),o.find(c=>c.textContent.toLocaleLowerCase().includes("delete")).click?.(),await b(1e3);const d=await w.getElementByXPath("/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div/div/div/div[3]/div/div/div/div/div[1]/div",{xpathFallback:"/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div[3]/div[2]/div/div[2]/div[1]"});console.log({btnDelete:d}),d?.click(),(await w.getElementByXPath("/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div[2]/div"))?.click(),await Mn(e,{published:!1}),chrome.runtime.sendMessage({type:"delete-done"})},Wn=chrome.runtime.connect();Wn.onMessage.addListener(async e=>{if(e.type==="publist-event"){const t=e.payload;if(!t)return;console.log("Received new product event:",t);try{await b(500),await zn(t)}catch(n){await ke(t,{error:n.message,published:!1})}finally{await ke(t,{published:!0}),await b(5e3),await Xn(t)}}});chrome.runtime.onMessage.addListener(e=>{e.type==="DELETE_STREAM_DATA"&&(console.log("Nhận dữ liệu từ background:",e.payload),Kn(e.payload))});async function Gn(){await Vn()}Gn(); +function Le(t,e){return function(){return t.apply(e,arguments)}}const{toString:rt}=Object.prototype,{getPrototypeOf:he}=Object,{iterator:Y,toStringTag:Ne}=Symbol,ee=(t=>e=>{const i=rt.call(e);return t[i]||(t[i]=i.slice(8,-1).toLowerCase())})(Object.create(null)),k=t=>(t=t.toLowerCase(),e=>ee(e)===t),te=t=>e=>typeof e===t,{isArray:j}=Array,$=te("undefined");function st(t){return t!==null&&!$(t)&&t.constructor!==null&&!$(t.constructor)&&A(t.constructor.isBuffer)&&t.constructor.isBuffer(t)}const Be=k("ArrayBuffer");function ot(t){let e;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?e=ArrayBuffer.isView(t):e=t&&t.buffer&&Be(t.buffer),e}const dt=te("string"),A=te("function"),Fe=te("number"),ie=t=>t!==null&&typeof t=="object",at=t=>t===!0||t===!1,K=t=>{if(ee(t)!=="object")return!1;const e=he(t);return(e===null||e===Object.prototype||Object.getPrototypeOf(e)===null)&&!(Ne in t)&&!(Y in t)},ct=k("Date"),lt=k("File"),ut=k("Blob"),vt=k("FileList"),ft=t=>ie(t)&&A(t.pipe),pt=t=>{let e;return t&&(typeof FormData=="function"&&t instanceof FormData||A(t.append)&&((e=ee(t))==="formdata"||e==="object"&&A(t.toString)&&t.toString()==="[object FormData]"))},ht=k("URLSearchParams"),[mt,wt,bt,yt]=["ReadableStream","Request","Response","Headers"].map(k),Et=t=>t.trim?t.trim():t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function z(t,e,{allOwnKeys:i=!1}={}){if(t===null||typeof t>"u")return;let n,r;if(typeof t!="object"&&(t=[t]),j(t))for(n=0,r=t.length;n0;)if(r=i[n],e===r.toLowerCase())return r;return null}const U=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,De=t=>!$(t)&&t!==U;function ce(){const{caseless:t}=De(this)&&this||{},e={},i=(n,r)=>{const s=t&&Ue(e,r)||r;K(e[s])&&K(n)?e[s]=ce(e[s],n):K(n)?e[s]=ce({},n):j(n)?e[s]=n.slice():e[s]=n};for(let n=0,r=arguments.length;n(z(e,(r,s)=>{i&&A(r)?t[s]=Le(r,i):t[s]=r},{allOwnKeys:n}),t),St=t=>(t.charCodeAt(0)===65279&&(t=t.slice(1)),t),Rt=(t,e,i,n)=>{t.prototype=Object.create(e.prototype,n),t.prototype.constructor=t,Object.defineProperty(t,"super",{value:e.prototype}),i&&Object.assign(t.prototype,i)},Tt=(t,e,i,n)=>{let r,s,o;const a={};if(e=e||{},t==null)return e;do{for(r=Object.getOwnPropertyNames(t),s=r.length;s-- >0;)o=r[s],(!n||n(o,t,e))&&!a[o]&&(e[o]=t[o],a[o]=!0);t=i!==!1&&he(t)}while(t&&(!i||i(t,e))&&t!==Object.prototype);return e},_t=(t,e,i)=>{t=String(t),(i===void 0||i>t.length)&&(i=t.length),i-=e.length;const n=t.indexOf(e,i);return n!==-1&&n===i},Ot=t=>{if(!t)return null;if(j(t))return t;let e=t.length;if(!Fe(e))return null;const i=new Array(e);for(;e-- >0;)i[e]=t[e];return i},At=(t=>e=>t&&e instanceof t)(typeof Uint8Array<"u"&&he(Uint8Array)),Pt=(t,e)=>{const n=(t&&t[Y]).call(t);let r;for(;(r=n.next())&&!r.done;){const s=r.value;e.call(t,s[0],s[1])}},kt=(t,e)=>{let i;const n=[];for(;(i=t.exec(e))!==null;)n.push(i);return n},xt=k("HTMLFormElement"),Ct=t=>t.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(i,n,r){return n.toUpperCase()+r}),ye=(({hasOwnProperty:t})=>(e,i)=>t.call(e,i))(Object.prototype),Lt=k("RegExp"),Ie=(t,e)=>{const i=Object.getOwnPropertyDescriptors(t),n={};z(i,(r,s)=>{let o;(o=e(r,s,t))!==!1&&(n[s]=o||r)}),Object.defineProperties(t,n)},Nt=t=>{Ie(t,(e,i)=>{if(A(t)&&["arguments","caller","callee"].indexOf(i)!==-1)return!1;const n=t[i];if(A(n)){if(e.enumerable=!1,"writable"in e){e.writable=!1;return}e.set||(e.set=()=>{throw Error("Can not rewrite read-only method '"+i+"'")})}})},Bt=(t,e)=>{const i={},n=r=>{r.forEach(s=>{i[s]=!0})};return j(t)?n(t):n(String(t).split(e)),i},Ft=()=>{},Ut=(t,e)=>t!=null&&Number.isFinite(t=+t)?t:e;function Dt(t){return!!(t&&A(t.append)&&t[Ne]==="FormData"&&t[Y])}const It=t=>{const e=new Array(10),i=(n,r)=>{if(ie(n)){if(e.indexOf(n)>=0)return;if(!("toJSON"in n)){e[r]=n;const s=j(n)?[]:{};return z(n,(o,a)=>{const u=i(o,r+1);!$(u)&&(s[a]=u)}),e[r]=void 0,s}}return n};return i(t,0)},qt=k("AsyncFunction"),jt=t=>t&&(ie(t)||A(t))&&A(t.then)&&A(t.catch),qe=((t,e)=>t?setImmediate:e?((i,n)=>(U.addEventListener("message",({source:r,data:s})=>{r===U&&s===i&&n.length&&n.shift()()},!1),r=>{n.push(r),U.postMessage(i,"*")}))(`axios@${Math.random()}`,[]):i=>setTimeout(i))(typeof setImmediate=="function",A(U.postMessage)),Mt=typeof queueMicrotask<"u"?queueMicrotask.bind(U):typeof process<"u"&&process.nextTick||qe,Ht=t=>t!=null&&A(t[Y]),d={isArray:j,isArrayBuffer:Be,isBuffer:st,isFormData:pt,isArrayBufferView:ot,isString:dt,isNumber:Fe,isBoolean:at,isObject:ie,isPlainObject:K,isReadableStream:mt,isRequest:wt,isResponse:bt,isHeaders:yt,isUndefined:$,isDate:ct,isFile:lt,isBlob:ut,isRegExp:Lt,isFunction:A,isStream:ft,isURLSearchParams:ht,isTypedArray:At,isFileList:vt,forEach:z,merge:ce,extend:gt,trim:Et,stripBOM:St,inherits:Rt,toFlatObject:Tt,kindOf:ee,kindOfTest:k,endsWith:_t,toArray:Ot,forEachEntry:Pt,matchAll:kt,isHTMLForm:xt,hasOwnProperty:ye,hasOwnProp:ye,reduceDescriptors:Ie,freezeMethods:Nt,toObjectSet:Bt,toCamelCase:Ct,noop:Ft,toFiniteNumber:Ut,findKey:Ue,global:U,isContextDefined:De,isSpecCompliantForm:Dt,toJSONObject:It,isAsyncFn:qt,isThenable:jt,setImmediate:qe,asap:Mt,isIterable:Ht};function h(t,e,i,n,r){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=t,this.name="AxiosError",e&&(this.code=e),i&&(this.config=i),n&&(this.request=n),r&&(this.response=r,this.status=r.status?r.status:null)}d.inherits(h,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:d.toJSONObject(this.config),code:this.code,status:this.status}}});const je=h.prototype,Me={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(t=>{Me[t]={value:t}});Object.defineProperties(h,Me);Object.defineProperty(je,"isAxiosError",{value:!0});h.from=(t,e,i,n,r,s)=>{const o=Object.create(je);return d.toFlatObject(t,o,function(u){return u!==Error.prototype},a=>a!=="isAxiosError"),h.call(o,t.message,e,i,n,r),o.cause=t,o.name=t.name,s&&Object.assign(o,s),o};const $t=null;function le(t){return d.isPlainObject(t)||d.isArray(t)}function He(t){return d.endsWith(t,"[]")?t.slice(0,-2):t}function Ee(t,e,i){return t?t.concat(e).map(function(r,s){return r=He(r),!i&&s?"["+r+"]":r}).join(i?".":""):e}function zt(t){return d.isArray(t)&&!t.some(le)}const Jt=d.toFlatObject(d,{},null,function(e){return/^is[A-Z]/.test(e)});function ne(t,e,i){if(!d.isObject(t))throw new TypeError("target must be an object");e=e||new FormData,i=d.toFlatObject(i,{metaTokens:!0,dots:!1,indexes:!1},!1,function(m,p){return!d.isUndefined(p[m])});const n=i.metaTokens,r=i.visitor||l,s=i.dots,o=i.indexes,u=(i.Blob||typeof Blob<"u"&&Blob)&&d.isSpecCompliantForm(e);if(!d.isFunction(r))throw new TypeError("visitor must be a function");function c(v){if(v===null)return"";if(d.isDate(v))return v.toISOString();if(d.isBoolean(v))return v.toString();if(!u&&d.isBlob(v))throw new h("Blob is not supported. Use a Buffer instead.");return d.isArrayBuffer(v)||d.isTypedArray(v)?u&&typeof Blob=="function"?new Blob([v]):Buffer.from(v):v}function l(v,m,p){let E=v;if(v&&!p&&typeof v=="object"){if(d.endsWith(m,"{}"))m=n?m:m.slice(0,-2),v=JSON.stringify(v);else if(d.isArray(v)&&zt(v)||(d.isFileList(v)||d.endsWith(m,"[]"))&&(E=d.toArray(v)))return m=He(m),E.forEach(function(T,N){!(d.isUndefined(T)||T===null)&&e.append(o===!0?Ee([m],N,s):o===null?m:m+"[]",c(T))}),!1}return le(v)?!0:(e.append(Ee(p,m,s),c(v)),!1)}const f=[],b=Object.assign(Jt,{defaultVisitor:l,convertValue:c,isVisitable:le});function S(v,m){if(!d.isUndefined(v)){if(f.indexOf(v)!==-1)throw Error("Circular reference detected in "+m.join("."));f.push(v),d.forEach(v,function(E,R){(!(d.isUndefined(E)||E===null)&&r.call(e,E,d.isString(R)?R.trim():R,m,b))===!0&&S(E,m?m.concat(R):[R])}),f.pop()}}if(!d.isObject(t))throw new TypeError("data must be an object");return S(t),e}function ge(t){const e={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(t).replace(/[!'()~]|%20|%00/g,function(n){return e[n]})}function me(t,e){this._pairs=[],t&&ne(t,this,e)}const $e=me.prototype;$e.append=function(e,i){this._pairs.push([e,i])};$e.toString=function(e){const i=e?function(n){return e.call(this,n,ge)}:ge;return this._pairs.map(function(r){return i(r[0])+"="+i(r[1])},"").join("&")};function Vt(t){return encodeURIComponent(t).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function ze(t,e,i){if(!e)return t;const n=i&&i.encode||Vt;d.isFunction(i)&&(i={serialize:i});const r=i&&i.serialize;let s;if(r?s=r(e,i):s=d.isURLSearchParams(e)?e.toString():new me(e,i).toString(n),s){const o=t.indexOf("#");o!==-1&&(t=t.slice(0,o)),t+=(t.indexOf("?")===-1?"?":"&")+s}return t}class Se{constructor(){this.handlers=[]}use(e,i,n){return this.handlers.push({fulfilled:e,rejected:i,synchronous:n?n.synchronous:!1,runWhen:n?n.runWhen:null}),this.handlers.length-1}eject(e){this.handlers[e]&&(this.handlers[e]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(e){d.forEach(this.handlers,function(n){n!==null&&e(n)})}}const Je={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},Xt=typeof URLSearchParams<"u"?URLSearchParams:me,Kt=typeof FormData<"u"?FormData:null,Wt=typeof Blob<"u"?Blob:null,Gt={isBrowser:!0,classes:{URLSearchParams:Xt,FormData:Kt,Blob:Wt},protocols:["http","https","file","blob","url","data"]},we=typeof window<"u"&&typeof document<"u",ue=typeof navigator=="object"&&navigator||void 0,Zt=we&&(!ue||["ReactNative","NativeScript","NS"].indexOf(ue.product)<0),Qt=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",Yt=we&&window.location.href||"http://localhost",ei=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:we,hasStandardBrowserEnv:Zt,hasStandardBrowserWebWorkerEnv:Qt,navigator:ue,origin:Yt},Symbol.toStringTag,{value:"Module"})),_={...ei,...Gt};function ti(t,e){return ne(t,new _.classes.URLSearchParams,Object.assign({visitor:function(i,n,r,s){return _.isNode&&d.isBuffer(i)?(this.append(n,i.toString("base64")),!1):s.defaultVisitor.apply(this,arguments)}},e))}function ii(t){return d.matchAll(/\w+|\[(\w*)]/g,t).map(e=>e[0]==="[]"?"":e[1]||e[0])}function ni(t){const e={},i=Object.keys(t);let n;const r=i.length;let s;for(n=0;n=i.length;return o=!o&&d.isArray(r)?r.length:o,u?(d.hasOwnProp(r,o)?r[o]=[r[o],n]:r[o]=n,!a):((!r[o]||!d.isObject(r[o]))&&(r[o]=[]),e(i,n,r[o],s)&&d.isArray(r[o])&&(r[o]=ni(r[o])),!a)}if(d.isFormData(t)&&d.isFunction(t.entries)){const i={};return d.forEachEntry(t,(n,r)=>{e(ii(n),r,i,0)}),i}return null}function ri(t,e,i){if(d.isString(t))try{return(e||JSON.parse)(t),d.trim(t)}catch(n){if(n.name!=="SyntaxError")throw n}return(i||JSON.stringify)(t)}const J={transitional:Je,adapter:["xhr","http","fetch"],transformRequest:[function(e,i){const n=i.getContentType()||"",r=n.indexOf("application/json")>-1,s=d.isObject(e);if(s&&d.isHTMLForm(e)&&(e=new FormData(e)),d.isFormData(e))return r?JSON.stringify(Ve(e)):e;if(d.isArrayBuffer(e)||d.isBuffer(e)||d.isStream(e)||d.isFile(e)||d.isBlob(e)||d.isReadableStream(e))return e;if(d.isArrayBufferView(e))return e.buffer;if(d.isURLSearchParams(e))return i.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();let a;if(s){if(n.indexOf("application/x-www-form-urlencoded")>-1)return ti(e,this.formSerializer).toString();if((a=d.isFileList(e))||n.indexOf("multipart/form-data")>-1){const u=this.env&&this.env.FormData;return ne(a?{"files[]":e}:e,u&&new u,this.formSerializer)}}return s||r?(i.setContentType("application/json",!1),ri(e)):e}],transformResponse:[function(e){const i=this.transitional||J.transitional,n=i&&i.forcedJSONParsing,r=this.responseType==="json";if(d.isResponse(e)||d.isReadableStream(e))return e;if(e&&d.isString(e)&&(n&&!this.responseType||r)){const o=!(i&&i.silentJSONParsing)&&r;try{return JSON.parse(e)}catch(a){if(o)throw a.name==="SyntaxError"?h.from(a,h.ERR_BAD_RESPONSE,this,null,this.response):a}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:_.classes.FormData,Blob:_.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};d.forEach(["delete","get","head","post","put","patch"],t=>{J.headers[t]={}});const si=d.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),oi=t=>{const e={};let i,n,r;return t&&t.split(` +`).forEach(function(o){r=o.indexOf(":"),i=o.substring(0,r).trim().toLowerCase(),n=o.substring(r+1).trim(),!(!i||e[i]&&si[i])&&(i==="set-cookie"?e[i]?e[i].push(n):e[i]=[n]:e[i]=e[i]?e[i]+", "+n:n)}),e},Re=Symbol("internals");function H(t){return t&&String(t).trim().toLowerCase()}function W(t){return t===!1||t==null?t:d.isArray(t)?t.map(W):String(t)}function di(t){const e=Object.create(null),i=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let n;for(;n=i.exec(t);)e[n[1]]=n[2];return e}const ai=t=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(t.trim());function oe(t,e,i,n,r){if(d.isFunction(n))return n.call(this,e,i);if(r&&(e=i),!!d.isString(e)){if(d.isString(n))return e.indexOf(n)!==-1;if(d.isRegExp(n))return n.test(e)}}function ci(t){return t.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(e,i,n)=>i.toUpperCase()+n)}function li(t,e){const i=d.toCamelCase(" "+e);["get","set","has"].forEach(n=>{Object.defineProperty(t,n+i,{value:function(r,s,o){return this[n].call(this,e,r,s,o)},configurable:!0})})}let P=class{constructor(e){e&&this.set(e)}set(e,i,n){const r=this;function s(a,u,c){const l=H(u);if(!l)throw new Error("header name must be a non-empty string");const f=d.findKey(r,l);(!f||r[f]===void 0||c===!0||c===void 0&&r[f]!==!1)&&(r[f||u]=W(a))}const o=(a,u)=>d.forEach(a,(c,l)=>s(c,l,u));if(d.isPlainObject(e)||e instanceof this.constructor)o(e,i);else if(d.isString(e)&&(e=e.trim())&&!ai(e))o(oi(e),i);else if(d.isObject(e)&&d.isIterable(e)){let a={},u,c;for(const l of e){if(!d.isArray(l))throw TypeError("Object iterator must return a key-value pair");a[c=l[0]]=(u=a[c])?d.isArray(u)?[...u,l[1]]:[u,l[1]]:l[1]}o(a,i)}else e!=null&&s(i,e,n);return this}get(e,i){if(e=H(e),e){const n=d.findKey(this,e);if(n){const r=this[n];if(!i)return r;if(i===!0)return di(r);if(d.isFunction(i))return i.call(this,r,n);if(d.isRegExp(i))return i.exec(r);throw new TypeError("parser must be boolean|regexp|function")}}}has(e,i){if(e=H(e),e){const n=d.findKey(this,e);return!!(n&&this[n]!==void 0&&(!i||oe(this,this[n],n,i)))}return!1}delete(e,i){const n=this;let r=!1;function s(o){if(o=H(o),o){const a=d.findKey(n,o);a&&(!i||oe(n,n[a],a,i))&&(delete n[a],r=!0)}}return d.isArray(e)?e.forEach(s):s(e),r}clear(e){const i=Object.keys(this);let n=i.length,r=!1;for(;n--;){const s=i[n];(!e||oe(this,this[s],s,e,!0))&&(delete this[s],r=!0)}return r}normalize(e){const i=this,n={};return d.forEach(this,(r,s)=>{const o=d.findKey(n,s);if(o){i[o]=W(r),delete i[s];return}const a=e?ci(s):String(s).trim();a!==s&&delete i[s],i[a]=W(r),n[a]=!0}),this}concat(...e){return this.constructor.concat(this,...e)}toJSON(e){const i=Object.create(null);return d.forEach(this,(n,r)=>{n!=null&&n!==!1&&(i[r]=e&&d.isArray(n)?n.join(", "):n)}),i}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([e,i])=>e+": "+i).join(` +`)}getSetCookie(){return this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(e){return e instanceof this?e:new this(e)}static concat(e,...i){const n=new this(e);return i.forEach(r=>n.set(r)),n}static accessor(e){const n=(this[Re]=this[Re]={accessors:{}}).accessors,r=this.prototype;function s(o){const a=H(o);n[a]||(li(r,o),n[a]=!0)}return d.isArray(e)?e.forEach(s):s(e),this}};P.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);d.reduceDescriptors(P.prototype,({value:t},e)=>{let i=e[0].toUpperCase()+e.slice(1);return{get:()=>t,set(n){this[i]=n}}});d.freezeMethods(P);function de(t,e){const i=this||J,n=e||i,r=P.from(n.headers);let s=n.data;return d.forEach(t,function(a){s=a.call(i,s,r.normalize(),e?e.status:void 0)}),r.normalize(),s}function Xe(t){return!!(t&&t.__CANCEL__)}function M(t,e,i){h.call(this,t??"canceled",h.ERR_CANCELED,e,i),this.name="CanceledError"}d.inherits(M,h,{__CANCEL__:!0});function Ke(t,e,i){const n=i.config.validateStatus;!i.status||!n||n(i.status)?t(i):e(new h("Request failed with status code "+i.status,[h.ERR_BAD_REQUEST,h.ERR_BAD_RESPONSE][Math.floor(i.status/100)-4],i.config,i.request,i))}function ui(t){const e=/^([-+\w]{1,25})(:?\/\/|:)/.exec(t);return e&&e[1]||""}function vi(t,e){t=t||10;const i=new Array(t),n=new Array(t);let r=0,s=0,o;return e=e!==void 0?e:1e3,function(u){const c=Date.now(),l=n[s];o||(o=c),i[r]=u,n[r]=c;let f=s,b=0;for(;f!==r;)b+=i[f++],f=f%t;if(r=(r+1)%t,r===s&&(s=(s+1)%t),c-o{i=l,r=null,s&&(clearTimeout(s),s=null),t.apply(null,c)};return[(...c)=>{const l=Date.now(),f=l-i;f>=n?o(c,l):(r=c,s||(s=setTimeout(()=>{s=null,o(r)},n-f)))},()=>r&&o(r)]}const Z=(t,e,i=3)=>{let n=0;const r=vi(50,250);return fi(s=>{const o=s.loaded,a=s.lengthComputable?s.total:void 0,u=o-n,c=r(u),l=o<=a;n=o;const f={loaded:o,total:a,progress:a?o/a:void 0,bytes:u,rate:c||void 0,estimated:c&&a&&l?(a-o)/c:void 0,event:s,lengthComputable:a!=null,[e?"download":"upload"]:!0};t(f)},i)},Te=(t,e)=>{const i=t!=null;return[n=>e[0]({lengthComputable:i,total:t,loaded:n}),e[1]]},_e=t=>(...e)=>d.asap(()=>t(...e)),pi=_.hasStandardBrowserEnv?((t,e)=>i=>(i=new URL(i,_.origin),t.protocol===i.protocol&&t.host===i.host&&(e||t.port===i.port)))(new URL(_.origin),_.navigator&&/(msie|trident)/i.test(_.navigator.userAgent)):()=>!0,hi=_.hasStandardBrowserEnv?{write(t,e,i,n,r,s){const o=[t+"="+encodeURIComponent(e)];d.isNumber(i)&&o.push("expires="+new Date(i).toGMTString()),d.isString(n)&&o.push("path="+n),d.isString(r)&&o.push("domain="+r),s===!0&&o.push("secure"),document.cookie=o.join("; ")},read(t){const e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove(t){this.write(t,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function mi(t){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(t)}function wi(t,e){return e?t.replace(/\/?\/$/,"")+"/"+e.replace(/^\/+/,""):t}function We(t,e,i){let n=!mi(e);return t&&(n||i==!1)?wi(t,e):e}const Oe=t=>t instanceof P?{...t}:t;function I(t,e){e=e||{};const i={};function n(c,l,f,b){return d.isPlainObject(c)&&d.isPlainObject(l)?d.merge.call({caseless:b},c,l):d.isPlainObject(l)?d.merge({},l):d.isArray(l)?l.slice():l}function r(c,l,f,b){if(d.isUndefined(l)){if(!d.isUndefined(c))return n(void 0,c,f,b)}else return n(c,l,f,b)}function s(c,l){if(!d.isUndefined(l))return n(void 0,l)}function o(c,l){if(d.isUndefined(l)){if(!d.isUndefined(c))return n(void 0,c)}else return n(void 0,l)}function a(c,l,f){if(f in e)return n(c,l);if(f in t)return n(void 0,c)}const u={url:s,method:s,data:s,baseURL:o,transformRequest:o,transformResponse:o,paramsSerializer:o,timeout:o,timeoutMessage:o,withCredentials:o,withXSRFToken:o,adapter:o,responseType:o,xsrfCookieName:o,xsrfHeaderName:o,onUploadProgress:o,onDownloadProgress:o,decompress:o,maxContentLength:o,maxBodyLength:o,beforeRedirect:o,transport:o,httpAgent:o,httpsAgent:o,cancelToken:o,socketPath:o,responseEncoding:o,validateStatus:a,headers:(c,l,f)=>r(Oe(c),Oe(l),f,!0)};return d.forEach(Object.keys(Object.assign({},t,e)),function(l){const f=u[l]||r,b=f(t[l],e[l],l);d.isUndefined(b)&&f!==a||(i[l]=b)}),i}const Ge=t=>{const e=I({},t);let{data:i,withXSRFToken:n,xsrfHeaderName:r,xsrfCookieName:s,headers:o,auth:a}=e;e.headers=o=P.from(o),e.url=ze(We(e.baseURL,e.url,e.allowAbsoluteUrls),t.params,t.paramsSerializer),a&&o.set("Authorization","Basic "+btoa((a.username||"")+":"+(a.password?unescape(encodeURIComponent(a.password)):"")));let u;if(d.isFormData(i)){if(_.hasStandardBrowserEnv||_.hasStandardBrowserWebWorkerEnv)o.setContentType(void 0);else if((u=o.getContentType())!==!1){const[c,...l]=u?u.split(";").map(f=>f.trim()).filter(Boolean):[];o.setContentType([c||"multipart/form-data",...l].join("; "))}}if(_.hasStandardBrowserEnv&&(n&&d.isFunction(n)&&(n=n(e)),n||n!==!1&&pi(e.url))){const c=r&&s&&hi.read(s);c&&o.set(r,c)}return e},bi=typeof XMLHttpRequest<"u",yi=bi&&function(t){return new Promise(function(i,n){const r=Ge(t);let s=r.data;const o=P.from(r.headers).normalize();let{responseType:a,onUploadProgress:u,onDownloadProgress:c}=r,l,f,b,S,v;function m(){S&&S(),v&&v(),r.cancelToken&&r.cancelToken.unsubscribe(l),r.signal&&r.signal.removeEventListener("abort",l)}let p=new XMLHttpRequest;p.open(r.method.toUpperCase(),r.url,!0),p.timeout=r.timeout;function E(){if(!p)return;const T=P.from("getAllResponseHeaders"in p&&p.getAllResponseHeaders()),O={data:!a||a==="text"||a==="json"?p.responseText:p.response,status:p.status,statusText:p.statusText,headers:T,config:t,request:p};Ke(function(F){i(F),m()},function(F){n(F),m()},O),p=null}"onloadend"in p?p.onloadend=E:p.onreadystatechange=function(){!p||p.readyState!==4||p.status===0&&!(p.responseURL&&p.responseURL.indexOf("file:")===0)||setTimeout(E)},p.onabort=function(){p&&(n(new h("Request aborted",h.ECONNABORTED,t,p)),p=null)},p.onerror=function(){n(new h("Network Error",h.ERR_NETWORK,t,p)),p=null},p.ontimeout=function(){let N=r.timeout?"timeout of "+r.timeout+"ms exceeded":"timeout exceeded";const O=r.transitional||Je;r.timeoutErrorMessage&&(N=r.timeoutErrorMessage),n(new h(N,O.clarifyTimeoutError?h.ETIMEDOUT:h.ECONNABORTED,t,p)),p=null},s===void 0&&o.setContentType(null),"setRequestHeader"in p&&d.forEach(o.toJSON(),function(N,O){p.setRequestHeader(O,N)}),d.isUndefined(r.withCredentials)||(p.withCredentials=!!r.withCredentials),a&&a!=="json"&&(p.responseType=r.responseType),c&&([b,v]=Z(c,!0),p.addEventListener("progress",b)),u&&p.upload&&([f,S]=Z(u),p.upload.addEventListener("progress",f),p.upload.addEventListener("loadend",S)),(r.cancelToken||r.signal)&&(l=T=>{p&&(n(!T||T.type?new M(null,t,p):T),p.abort(),p=null)},r.cancelToken&&r.cancelToken.subscribe(l),r.signal&&(r.signal.aborted?l():r.signal.addEventListener("abort",l)));const R=ui(r.url);if(R&&_.protocols.indexOf(R)===-1){n(new h("Unsupported protocol "+R+":",h.ERR_BAD_REQUEST,t));return}p.send(s||null)})},Ei=(t,e)=>{const{length:i}=t=t?t.filter(Boolean):[];if(e||i){let n=new AbortController,r;const s=function(c){if(!r){r=!0,a();const l=c instanceof Error?c:this.reason;n.abort(l instanceof h?l:new M(l instanceof Error?l.message:l))}};let o=e&&setTimeout(()=>{o=null,s(new h(`timeout ${e} of ms exceeded`,h.ETIMEDOUT))},e);const a=()=>{t&&(o&&clearTimeout(o),o=null,t.forEach(c=>{c.unsubscribe?c.unsubscribe(s):c.removeEventListener("abort",s)}),t=null)};t.forEach(c=>c.addEventListener("abort",s));const{signal:u}=n;return u.unsubscribe=()=>d.asap(a),u}},gi=function*(t,e){let i=t.byteLength;if(i{const r=Si(t,e);let s=0,o,a=u=>{o||(o=!0,n&&n(u))};return new ReadableStream({async pull(u){try{const{done:c,value:l}=await r.next();if(c){a(),u.close();return}let f=l.byteLength;if(i){let b=s+=f;i(b)}u.enqueue(new Uint8Array(l))}catch(c){throw a(c),c}},cancel(u){return a(u),r.return()}},{highWaterMark:2})},re=typeof fetch=="function"&&typeof Request=="function"&&typeof Response=="function",Ze=re&&typeof ReadableStream=="function",Ti=re&&(typeof TextEncoder=="function"?(t=>e=>t.encode(e))(new TextEncoder):async t=>new Uint8Array(await new Response(t).arrayBuffer())),Qe=(t,...e)=>{try{return!!t(...e)}catch{return!1}},_i=Ze&&Qe(()=>{let t=!1;const e=new Request(_.origin,{body:new ReadableStream,method:"POST",get duplex(){return t=!0,"half"}}).headers.has("Content-Type");return t&&!e}),Pe=64*1024,ve=Ze&&Qe(()=>d.isReadableStream(new Response("").body)),Q={stream:ve&&(t=>t.body)};re&&(t=>{["text","arrayBuffer","blob","formData","stream"].forEach(e=>{!Q[e]&&(Q[e]=d.isFunction(t[e])?i=>i[e]():(i,n)=>{throw new h(`Response type '${e}' is not supported`,h.ERR_NOT_SUPPORT,n)})})})(new Response);const Oi=async t=>{if(t==null)return 0;if(d.isBlob(t))return t.size;if(d.isSpecCompliantForm(t))return(await new Request(_.origin,{method:"POST",body:t}).arrayBuffer()).byteLength;if(d.isArrayBufferView(t)||d.isArrayBuffer(t))return t.byteLength;if(d.isURLSearchParams(t)&&(t=t+""),d.isString(t))return(await Ti(t)).byteLength},Ai=async(t,e)=>{const i=d.toFiniteNumber(t.getContentLength());return i??Oi(e)},Pi=re&&(async t=>{let{url:e,method:i,data:n,signal:r,cancelToken:s,timeout:o,onDownloadProgress:a,onUploadProgress:u,responseType:c,headers:l,withCredentials:f="same-origin",fetchOptions:b}=Ge(t);c=c?(c+"").toLowerCase():"text";let S=Ei([r,s&&s.toAbortSignal()],o),v;const m=S&&S.unsubscribe&&(()=>{S.unsubscribe()});let p;try{if(u&&_i&&i!=="get"&&i!=="head"&&(p=await Ai(l,n))!==0){let O=new Request(e,{method:"POST",body:n,duplex:"half"}),B;if(d.isFormData(n)&&(B=O.headers.get("content-type"))&&l.setContentType(B),O.body){const[F,V]=Te(p,Z(_e(u)));n=Ae(O.body,Pe,F,V)}}d.isString(f)||(f=f?"include":"omit");const E="credentials"in Request.prototype;v=new Request(e,{...b,signal:S,method:i.toUpperCase(),headers:l.normalize().toJSON(),body:n,duplex:"half",credentials:E?f:void 0});let R=await fetch(v,b);const T=ve&&(c==="stream"||c==="response");if(ve&&(a||T&&m)){const O={};["status","statusText","headers"].forEach(be=>{O[be]=R[be]});const B=d.toFiniteNumber(R.headers.get("content-length")),[F,V]=a&&Te(B,Z(_e(a),!0))||[];R=new Response(Ae(R.body,Pe,F,()=>{V&&V(),m&&m()}),O)}c=c||"text";let N=await Q[d.findKey(Q,c)||"text"](R,t);return!T&&m&&m(),await new Promise((O,B)=>{Ke(O,B,{data:N,headers:P.from(R.headers),status:R.status,statusText:R.statusText,config:t,request:v})})}catch(E){throw m&&m(),E&&E.name==="TypeError"&&/Load failed|fetch/i.test(E.message)?Object.assign(new h("Network Error",h.ERR_NETWORK,t,v),{cause:E.cause||E}):h.from(E,E&&E.code,t,v)}}),fe={http:$t,xhr:yi,fetch:Pi};d.forEach(fe,(t,e)=>{if(t){try{Object.defineProperty(t,"name",{value:e})}catch{}Object.defineProperty(t,"adapterName",{value:e})}});const ke=t=>`- ${t}`,ki=t=>d.isFunction(t)||t===null||t===!1,Ye={getAdapter:t=>{t=d.isArray(t)?t:[t];const{length:e}=t;let i,n;const r={};for(let s=0;s`adapter ${a} `+(u===!1?"is not supported by the environment":"is not available in the build"));let o=e?s.length>1?`since : +`+s.map(ke).join(` +`):" "+ke(s[0]):"as no adapter specified";throw new h("There is no suitable adapter to dispatch the request "+o,"ERR_NOT_SUPPORT")}return n},adapters:fe};function ae(t){if(t.cancelToken&&t.cancelToken.throwIfRequested(),t.signal&&t.signal.aborted)throw new M(null,t)}function xe(t){return ae(t),t.headers=P.from(t.headers),t.data=de.call(t,t.transformRequest),["post","put","patch"].indexOf(t.method)!==-1&&t.headers.setContentType("application/x-www-form-urlencoded",!1),Ye.getAdapter(t.adapter||J.adapter)(t).then(function(n){return ae(t),n.data=de.call(t,t.transformResponse,n),n.headers=P.from(n.headers),n},function(n){return Xe(n)||(ae(t),n&&n.response&&(n.response.data=de.call(t,t.transformResponse,n.response),n.response.headers=P.from(n.response.headers))),Promise.reject(n)})}const et="1.10.0",se={};["object","boolean","number","function","string","symbol"].forEach((t,e)=>{se[t]=function(n){return typeof n===t||"a"+(e<1?"n ":" ")+t}});const Ce={};se.transitional=function(e,i,n){function r(s,o){return"[Axios v"+et+"] Transitional option '"+s+"'"+o+(n?". "+n:"")}return(s,o,a)=>{if(e===!1)throw new h(r(o," has been removed"+(i?" in "+i:"")),h.ERR_DEPRECATED);return i&&!Ce[o]&&(Ce[o]=!0,console.warn(r(o," has been deprecated since v"+i+" and will be removed in the near future"))),e?e(s,o,a):!0}};se.spelling=function(e){return(i,n)=>(console.warn(`${n} is likely a misspelling of ${e}`),!0)};function xi(t,e,i){if(typeof t!="object")throw new h("options must be an object",h.ERR_BAD_OPTION_VALUE);const n=Object.keys(t);let r=n.length;for(;r-- >0;){const s=n[r],o=e[s];if(o){const a=t[s],u=a===void 0||o(a,s,t);if(u!==!0)throw new h("option "+s+" must be "+u,h.ERR_BAD_OPTION_VALUE);continue}if(i!==!0)throw new h("Unknown option "+s,h.ERR_BAD_OPTION)}}const G={assertOptions:xi,validators:se},x=G.validators;let D=class{constructor(e){this.defaults=e||{},this.interceptors={request:new Se,response:new Se}}async request(e,i){try{return await this._request(e,i)}catch(n){if(n instanceof Error){let r={};Error.captureStackTrace?Error.captureStackTrace(r):r=new Error;const s=r.stack?r.stack.replace(/^.+\n/,""):"";try{n.stack?s&&!String(n.stack).endsWith(s.replace(/^.+\n.+\n/,""))&&(n.stack+=` +`+s):n.stack=s}catch{}}throw n}}_request(e,i){typeof e=="string"?(i=i||{},i.url=e):i=e||{},i=I(this.defaults,i);const{transitional:n,paramsSerializer:r,headers:s}=i;n!==void 0&&G.assertOptions(n,{silentJSONParsing:x.transitional(x.boolean),forcedJSONParsing:x.transitional(x.boolean),clarifyTimeoutError:x.transitional(x.boolean)},!1),r!=null&&(d.isFunction(r)?i.paramsSerializer={serialize:r}:G.assertOptions(r,{encode:x.function,serialize:x.function},!0)),i.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?i.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:i.allowAbsoluteUrls=!0),G.assertOptions(i,{baseUrl:x.spelling("baseURL"),withXsrfToken:x.spelling("withXSRFToken")},!0),i.method=(i.method||this.defaults.method||"get").toLowerCase();let o=s&&d.merge(s.common,s[i.method]);s&&d.forEach(["delete","get","head","post","put","patch","common"],v=>{delete s[v]}),i.headers=P.concat(o,s);const a=[];let u=!0;this.interceptors.request.forEach(function(m){typeof m.runWhen=="function"&&m.runWhen(i)===!1||(u=u&&m.synchronous,a.unshift(m.fulfilled,m.rejected))});const c=[];this.interceptors.response.forEach(function(m){c.push(m.fulfilled,m.rejected)});let l,f=0,b;if(!u){const v=[xe.bind(this),void 0];for(v.unshift.apply(v,a),v.push.apply(v,c),b=v.length,l=Promise.resolve(i);f{if(!n._listeners)return;let s=n._listeners.length;for(;s-- >0;)n._listeners[s](r);n._listeners=null}),this.promise.then=r=>{let s;const o=new Promise(a=>{n.subscribe(a),s=a}).then(r);return o.cancel=function(){n.unsubscribe(s)},o},e(function(s,o,a){n.reason||(n.reason=new M(s,o,a),i(n.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this._listeners.push(e):this._listeners=[e]}unsubscribe(e){if(!this._listeners)return;const i=this._listeners.indexOf(e);i!==-1&&this._listeners.splice(i,1)}toAbortSignal(){const e=new AbortController,i=n=>{e.abort(n)};return this.subscribe(i),e.signal.unsubscribe=()=>this.unsubscribe(i),e.signal}static source(){let e;return{token:new tt(function(r){e=r}),cancel:e}}};function Li(t){return function(i){return t.apply(null,i)}}function Ni(t){return d.isObject(t)&&t.isAxiosError===!0}const pe={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(pe).forEach(([t,e])=>{pe[e]=t});function it(t){const e=new D(t),i=Le(D.prototype.request,e);return d.extend(i,D.prototype,e,{allOwnKeys:!0}),d.extend(i,e,null,{allOwnKeys:!0}),i.create=function(r){return it(I(t,r))},i}const g=it(J);g.Axios=D;g.CanceledError=M;g.CancelToken=Ci;g.isCancel=Xe;g.VERSION=et;g.toFormData=ne;g.AxiosError=h;g.Cancel=g.CanceledError;g.all=function(e){return Promise.all(e)};g.spread=Li;g.isAxiosError=Ni;g.mergeConfig=I;g.AxiosHeaders=P;g.formToJSON=t=>Ve(d.isHTMLForm(t)?new FormData(t):t);g.getAdapter=Ye.getAdapter;g.HttpStatusCode=pe;g.default=g;const{Axios:ji,AxiosError:Mi,CanceledError:Hi,isCancel:$i,CancelToken:zi,VERSION:Ji,all:Vi,Cancel:Xi,isAxiosError:Ki,spread:Wi,toFormData:Gi,AxiosHeaders:Zi,HttpStatusCode:Qi,formToJSON:Yi,getAdapter:en,mergeConfig:tn}=g,X=g.create({baseURL:"http://localhost:4000/api/v1",headers:{"Content-Type":"application/json"}});class Bi{async sync(e){try{const{data:i}=await X.post("/products/sync",{items:e});return console.log("[NestJS] Response (bulk):",i),i}catch(i){throw console.error("[NestJS] Error (bulk):",i),i}}finistPublist=async(e,i)=>{const{data:n}=await X({url:"products/publist-finish/"+e.id,method:"POST",data:i});return n};updatePublist=async(e,i)=>{const{data:n}=await X({url:"products/update-finish/"+e.id,method:"POST",data:i});return n};finistDelete=async(e,i)=>{const{data:n}=await X({url:"products/delete-finish/"+e.id,method:"POST",data:i});return n}}const q=new Bi;function C(t){return new Promise(e=>setTimeout(e,t))}function y(t,e){const i=Math.floor(Math.random()*(e-t+1))+t;return C(i)}class Fi{base64ToFile(e,i,n){const r=e.includes(",")?e.split(",")[1]:e,s=atob(r),o=new ArrayBuffer(s.length),a=new Uint8Array(o);for(let c=0;c{let o=0,a=!1;const u=()=>{const c=document.evaluate(a&&r?r:e,document,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;if(c instanceof HTMLElement){s(c);return}o++,o{const o=new FileReader;o.onloadend=()=>{typeof o.result=="string"?r(o.result.split(",")[1]):s("Không thể đọc dữ liệu ảnh")},o.onerror=s,o.readAsDataURL(n)})}getImageExtension(e){try{const n=new URL(e).pathname.match(/\.([a-zA-Z0-9]+)$/);return n?n[1].toLowerCase():null}catch{const r=e.split("?")[0].match(/\.([a-zA-Z0-9]+)$/);return r?r[1].toLowerCase():null}}imageLocalToBase64(e){return new Promise((i,n)=>{try{const r=chrome.runtime.getURL(`${e}`);fetch(r).then(s=>s.blob()).then(s=>{const o=new FileReader;o.onloadend=()=>i(o.result),o.onerror=n,o.readAsDataURL(s)}).catch(n)}catch(r){n(r)}})}scrollToElement(e,i="smooth"){e&&e.scrollIntoView({behavior:i,block:"center",inline:"nearest"})}getElementPointCoores(e){if(!e)return null;const i=e.getBoundingClientRect(),n=i.left+i.width/2,r=i.top+i.height/2;return{x:n,y:r}}setInputValue(e,i){e&&(e.value=i,e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0})))}writeToInput=async(e,i,n)=>{const r=await this.getElementByXPath(i,{xpathFallback:n});if(!r)throw new Error("Xpath is not found with value: "+e);this.scrollToElement(r),this.clickByPoint(r),this.setInputValue(r,e)};pressEnter(e){if(!e)throw new Error("Textarea not found:",e);e.focus(),["keydown","keypress","keyup"].forEach(i=>{e.dispatchEvent(new KeyboardEvent(i,{key:"Enter",code:"Enter",keyCode:13,which:13,bubbles:!0,cancelable:!0}))})}}const w=new Fi;class Ui{sellingPath="https://www.facebook.com/marketplace/you/selling";selectors={file__image_input:'input[type="file"]',title_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div[2]/div/div/div[5]/div/div/div/label/div/input",price_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[6]/div/div/div/label/div/input",brand_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[2]/div/div/div/label/div/input",brand_input_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[2]/div/div/div/label/div/input",description_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[3]/div/div/div/label/div/div/textarea",description_input_falback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[3]/div/div/div/label/div/div/textarea",sku_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[6]/div/div/div[1]/label/div/input",sku_input_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[6]/div/div/div[1]/label/div/input",category_select:{wraper:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[7]/div/div/div/div",container:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/div/div/div/div/span/div"},condition_select:{wraper:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[8]/div/div/div/div",container:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/div/div/div/div[1]/div"},tags_input:{input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea",input_falback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea",plus_btn:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div[2]"},tags_edit_input:{input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[4]/div/div/div/div[1]/label/div/div/div[2]/div[1]/textarea",input_falback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea",plus_btn:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div[2]"},location_select:{input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input",input_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input",wraper:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div",container:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul",container_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul"},location_edit_select:{input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[6]/div/div/div/div/div/div/div/div/label/div[2]/input",input_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input",wraper:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div",container:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul",container_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul"},next_btn:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[5]/div/div/div",update_btn:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[4]/div/div/div",publish_btn:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[4]/div[2]/div/div",products:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[2]/div/div/div[2]/div[1]/div/div[2]/div/div/span/div/div",products_fallback:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[2]/div/div/div[2]/div[1]/div/div[2]/div[2]/div",option_btn:"/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div/div/div/div[3]/div/div/div/div/div[1]/div",option_btn_fallback:"/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div[3]/div[2]/div/div[2]/div[1]",close_btn_modal_feedback:"/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div[2]/div",images_container:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div[2]/div/div/div[3]/div[2]/div",description_edit_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[2]/div/div/div/label/div/div/textarea",sku_edit_input:"/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div[1]/label/div/input"};clearImages=async()=>{const e=await w.getElementByXPath(this.selectors.images_container);if(!e)throw new Error("Can't not found xpath images_container");const i=Array.from(e.children);console.log(i),i.forEach(n=>n.querySelector('[aria-label="Remove"]')?.click())};uploadImages=async e=>{const i=new DataTransfer;for(const r of e.images){const s=await w.imageUrlToBase64(r);console.log("Base64:",r.slice(0,50)+"...");const o=w.base64ToFile(s,e.sku,w.getImageExtension(r)||"jpg");i.items.add(o)}const n=document.querySelector(this.selectors.file__image_input);n?(n.files=i.files,n.dispatchEvent(new Event("change",{bubbles:!0}))):console.error("Không tìm thấy input[type='file']")};chooseSelect=async(e,i)=>{const n=await w.getElementByXPath(i.wraper);if(!n)throw new Error("Wrapper xpath not found");w.scrollToElement(n),w.clickByPoint(n),await y(300,500);const r=await w.getElementByXPath(i.container);if(!r)throw new Error("Container xpath not found");const s=Array.from(r.children).find(o=>o.textContent?.trim().toLocaleLowerCase().replace(/–/g,"-").includes(e.toLocaleLowerCase()));if(!s)throw new Error(`No child found with text "${e}"`);w.scrollToElement(s),await C(200),w.clickByPoint(s)};chooseLocation=async(e,{input:i,...n})=>{await w.writeToInput(e,i,n.input_fallback),await C(200);const r=await w.getElementByXPath(n.container,{xpathFallback:n.container_fallback});if(!r)throw new Error("Container xpath not found");w.scrollToElement(r);const s=Array.from(r.children).find(o=>o.textContent?.trim().toLocaleLowerCase().includes(e.toLocaleLowerCase()));if(!s)throw new Error(`No child found with text "${e}"`);w.scrollToElement(s),await C(200),w.clickByPoint(s)};writeTags=async(e,i)=>{const n=await w.getElementByXPath(i.input,{xpathFallback:i?.input_falback});if(!n)throw new Error("Input is not found");w.scrollToElement(n),await C(200);for(const r of e)await w.writeToInput(r,i.input,i?.input_falback),await C(200),w.pressEnter(n)};clickNext=async()=>{const e=await w.getElementByXPath(this.selectors.next_btn);if(!e)throw new Error("Next button is not found");w.clickByPoint(e)};clickUpdate=async()=>{const e=await w.getElementByXPath(this.selectors.update_btn);if(!e)throw new Error("Next button is not found");w.clickByPoint(e)};clickPublist=async()=>{const e=await w.getElementByXPath(this.selectors.publish_btn);if(!e)throw new Error("Publist button is not found");w.clickByPoint(e)};handlePublist=async e=>(console.log({item:e}),await y(1e3,2e3),await this.uploadImages(e),await y(300,500),w.writeToInput(e.title,this.selectors.title_input),await y(300,500),w.writeToInput(String(e.price),this.selectors.price_input),await y(300,500),await this.chooseSelect(e.category,this.selectors.category_select),await y(300,500),await this.chooseSelect(e.condition,this.selectors.condition_select),e.brand&&(await y(300,500),await w.writeToInput(e.brand,this.selectors.brand_input,this.selectors.brand_input_fallback)),await y(300,500),await w.writeToInput(e.description,this.selectors.description_input,this.selectors.description_input_falback),await y(300,500),await this.writeTags(e.tags,this.selectors.tags_input),await y(300,500),await w.writeToInput(e.sku,this.selectors.sku_input,this.selectors.sku_input_fallback),e?.location&&(await y(300,500),await this.chooseLocation(e.location,this.selectors.location_select)),await y(300,500),await this.clickNext(),await y(300,500),await this.clickPublist(),!0);handleUpdate=async e=>(console.log({item:e}),await y(1e3,2e3),await this.clearImages(),await y(1e3,2e3),await this.uploadImages(e),await y(1e3,2e3),w.writeToInput(e.title,this.selectors.title_input),await y(300,500),w.writeToInput(String(e.price),this.selectors.price_input),await y(300,500),await this.chooseSelect(e.category,this.selectors.category_select),await y(300,500),await this.chooseSelect(e.condition,this.selectors.condition_select),await y(300,500),await w.writeToInput(e.description,this.selectors.description_edit_input),await y(300,500),await this.writeTags(e.tags,this.selectors.tags_edit_input),await y(300,500),await w.writeToInput(e.sku,this.selectors.sku_edit_input),e?.location&&(await y(300,500),await this.chooseLocation(e.location,this.selectors.location_edit_select)),await y(300,500),await this.clickUpdate(),!0);getProducts=async()=>{const e=await w.getElementByXPath(this.selectors.products),i=await w.getElementByXPath(this.selectors.products_fallback),n=[e,i].filter(Boolean);return n.length===0?[]:n.flatMap(r=>this.extractListings(r))};extractListings(e){return Array.from(e.children).map(n=>{const s=n.querySelector('span[dir="auto"], div[dir="auto"]')?.textContent?.trim()||"",a=Array.from(n.querySelectorAll('span[dir="auto"]')).find(c=>/\d/.test(c.textContent||"")&&/[AU$]/.test(c.textContent||""))?.textContent?.match(/[\d,]+(?:\.\d+)?/),u=a?parseFloat(a[0].replace(/,/g,"")):0;return{title:s,price:u,el:e}})}closeTab=async e=>{chrome.runtime.sendMessage({type:"close-tab",payload:e})};clickOptionOfProduct(e,i){const n=e.querySelector(`[aria-label="More options for ${i.title}"]`);if(!n)throw new Error(`Not found option buttin in product ${i.title}, ID: ${i.id}`);n.click?.()}async getOptionEls(e,i){return this.clickOptionOfProduct(e,i),await C(2e3),Array.from(document.querySelectorAll('[role="menuitem"]'))}clickItemInList(e,i){const n=e.find(r=>r.textContent.toLocaleLowerCase().includes(i));if(!n)throw new Error(`Not found item ${i} in options list`);n.click?.()}getItemInList(e,i){const n=e.find(r=>r.textContent.toLocaleLowerCase().includes(i));if(!n)throw new Error(`Not found item ${i} in options list`);return n}handleDelete=async e=>{const i=await L.getProducts(),n=i.find(u=>u.title==e.title&&u.price==e.price);if(console.log({payload:e,product:n,products:i}),!n)return;const r=n.el,s=await this.getOptionEls(r,e);console.log({items:s}),this.clickItemInList(s,"delete"),await C(1e3);const o=await w.getElementByXPath(this.selectors.option_btn,{xpathFallback:this.selectors.option_btn_fallback});console.log({confirmBtn:o}),o?.click(),(await w.getElementByXPath(this.selectors.close_btn_modal_feedback))?.click(),await q.finistDelete(e,{published:!1}),chrome.runtime.sendMessage({type:"delete-done"})};syncListing=async()=>{if(!window.location.href.includes(this.sellingPath))return;const i=await L.getProducts(),n=await q.sync(i.map(r=>({title:r.title,price:r.price})));console.log({response:n})};extractMarketplaceItemId(e){const i=e.match(/\/marketplace\/item\/(\d+)/);return i?i[1]:null}handleGetPublistID=async e=>{const i=await L.getProducts(),n=i.find(u=>u.title==e.title&&u.price==e.price);if(console.log({payload:e,product:n,products:i}),!n)return;const r=n.el,s=await this.getOptionEls(r,e),o=this.getItemInList(s,"view listing"),a=this.extractMarketplaceItemId(o.href);return this.clickOptionOfProduct(r,e),a}}const L=new Ui,nt=chrome.runtime.connect();nt.onMessage.addListener(async t=>{if(t.type==="PUBLIST_EVENT"){const e=t.payload;if(!e)return;console.log("[PUBLIST_EVENT] Received new product event:",e);try{await y(500,600),await L.handlePublist(e)}catch(i){await q.finistPublist(e,{error:i.message,published:!1})}finally{await q.finistPublist(e,{published:!0}),await C(5e3),await L.closeTab(e)}}});nt.onMessage.addListener(async t=>{if(t.type==="EDIT_EVENT"){const{data:e,prev:i}=t.payload;if(!e)return;console.log("[PUBLIST_EVENT] Received new product event:",e);try{await L.handleUpdate(e)}catch(n){await q.updatePublist(e,{error:n.message,published:!0,publist_id:i.publist_id})}finally{await C(3e3),await q.updatePublist(e,{published:!0,message:`Edited product ${e.title}`,publist_id:i.publist_id}),await L.closeTab(e)}}});chrome.runtime.onMessage.addListener(async t=>{if(t.type==="DELETE_STREAM_DATA"){const e=t.payload;if(!e)return;console.log("[DELETE_STREAM_DATA] Received new product event:",e);try{await L.handleDelete(t.payload)}catch(i){await q.finistPublist(e,{error:i.message,published:!1})}}});chrome.runtime.onMessage.addListener(async t=>{if(t.type==="GET_PUBLIST_ID"){console.log({message:t});const{data:{prev:e}}=t,i=await L.handleGetPublistID(e);chrome.runtime.sendMessage({type:"GET_PUBLIST_ID_DONE",publist_id:i})}return!0});async function Di(){await L.syncListing()}Di(); diff --git a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/icons/128.png b/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/icons/128.png deleted file mode 100644 index c7de32f..0000000 Binary files a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/icons/128.png and /dev/null differ diff --git a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/icons/16.png b/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/icons/16.png deleted file mode 100644 index 46986c5..0000000 Binary files a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/icons/16.png and /dev/null differ diff --git a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/icons/32.png b/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/icons/32.png deleted file mode 100644 index b82bd11..0000000 Binary files a/auto-listing-facebook-marketplace/auto-listing-facebook-marketplace/icons/32.png and /dev/null differ diff --git a/auto-listing-facebook-marketplace/package.json b/auto-listing-facebook-marketplace/package.json index 81d6843..e0e1d71 100644 --- a/auto-listing-facebook-marketplace/package.json +++ b/auto-listing-facebook-marketplace/package.json @@ -7,7 +7,6 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview", "dev:build": "vite build --watch" }, "dependencies": { diff --git a/auto-listing-facebook-marketplace/src/api/product-api.service.ts b/auto-listing-facebook-marketplace/src/api/product-api.service.ts index 8a65e1f..3d371e0 100644 --- a/auto-listing-facebook-marketplace/src/api/product-api.service.ts +++ b/auto-listing-facebook-marketplace/src/api/product-api.service.ts @@ -13,6 +13,50 @@ class ProductApiService { throw err; } } + + finistPublist = async ( + item: IItem, + values: { error?: string; published: boolean; message?: string } + ) => { + const { data } = await axios({ + url: "products/publist-finish/" + item.id, + method: "POST", + data: values, + }); + + return data; + }; + + updatePublist = async ( + item: IItem, + values: { + error?: string; + published: boolean; + message?: string; + publist_id?: string; + } + ) => { + const { data } = await axios({ + url: "products/update-finish/" + item.id, + method: "POST", + data: values, + }); + + return data; + }; + + finistDelete = async ( + item: IItem, + values: { error?: string; published: boolean } + ) => { + const { data } = await axios({ + url: "products/delete-finish/" + item.id, + method: "POST", + data: values, + }); + + return data; + }; } export const productApi = new ProductApiService(); diff --git a/auto-listing-facebook-marketplace/src/background.ts b/auto-listing-facebook-marketplace/src/background.ts index 3506886..9f0a8ab 100644 --- a/auto-listing-facebook-marketplace/src/background.ts +++ b/auto-listing-facebook-marketplace/src/background.ts @@ -11,7 +11,6 @@ chrome.runtime.onConnect.addListener((port) => { }); }); -// eslint-disable-next-line @typescript-eslint/no-explicit-any async function handlePublish(data: any, timeoutMs = 5 * 60 * 1000) { return new Promise((resolve) => { chrome.tabs.create( @@ -33,7 +32,7 @@ async function handlePublish(data: any, timeoutMs = 5 * 60 * 1000) { const onConnectListener = (port: chrome.runtime.Port) => { if (port.sender?.tab?.id === tabId) { - port.postMessage({ type: "publist-event", payload: data }); + port.postMessage({ type: "PUBLIST_EVENT", payload: data }); chrome.runtime.onConnect.removeListener(onConnectListener); } }; @@ -57,6 +56,75 @@ async function handlePublish(data: any, timeoutMs = 5 * 60 * 1000) { }); } +async function handleEdits( + data: { prev: IItem; data: IItem }, + timeoutMs = 5 * 60 * 1000 +) { + return new Promise((resolve, reject) => { + (async () => { + try { + if (!data.prev?.publist_id) { + const response = await sendMessageToSellingTab( + { type: "GET_PUBLIST_ID", data }, + 30000 + ); + + console.log({ response }); + data.prev.publist_id = response.publist_id; + } + + chrome.tabs.create( + { + url: `https://www.facebook.com/marketplace/edit/?listing_id=${data.prev.publist_id}`, + active: true, + }, + (tab) => { + if (!tab?.id) resolve(data); + const tabId = tab.id; + + let resolved = false; + const cleanup = () => { + if (resolved) return; + resolved = true; + chrome.runtime.onConnect.removeListener(onConnectListener); + chrome.tabs.onRemoved.removeListener(onTabClosed); + clearTimeout(timeoutId); + resolve(data); + ensureMarketplaceSellingTab(); + }; + + const onConnectListener = (port: chrome.runtime.Port) => { + if (port.sender?.tab?.id === tabId) { + port.postMessage({ type: "EDIT_EVENT", payload: data }); + chrome.runtime.onConnect.removeListener(onConnectListener); + } + }; + chrome.runtime.onConnect.addListener(onConnectListener); + + const onTabClosed = (closedTabId: number) => { + if (closedTabId === tabId) { + cleanup(); + } + }; + chrome.tabs.onRemoved.addListener(onTabClosed); + + // Timeout tùy chỉnh + const timeoutId = setTimeout(() => { + console.warn( + `Tab ${tabId} timeout (${timeoutMs}ms) - auto resolve` + ); + if (tabId) chrome.tabs.remove(tabId); + cleanup(); + }, timeoutMs); + } + ); + } catch (err) { + reject(err); + } + })(); + }); +} + const handleListenPublists = () => { const evtSource = new EventSource( `${import.meta.env.VITE_API_URL}/products/publist-stream` @@ -64,7 +132,7 @@ const handleListenPublists = () => { evtSource.onmessage = (event) => { const data = JSON.parse(event.data); - console.log("New event:", data); + console.log("[PUBLIST] New event:", data); queue.add(() => handlePublish(data, 30000)); }; @@ -73,6 +141,45 @@ const handleListenPublists = () => { }; }; +function sendMessageToSellingTab( + message: any, + timeoutMs = 10000 +): Promise { + return new Promise((resolve, reject) => { + chrome.tabs.query( + { url: "*://www.facebook.com/marketplace/you/selling*" }, + (tabs) => { + if (tabs.length > 0 && tabs[0].id !== undefined) { + const tabId = tabs[0].id; + + const listener = (msg: any) => { + if (msg?.type === "GET_PUBLIST_ID_DONE") { + cleanup(); + resolve(msg); + } + }; + + const cleanup = () => { + chrome.runtime.onMessage.removeListener(listener); + clearTimeout(timeoutId); + }; + + chrome.runtime.onMessage.addListener(listener); + + const timeoutId = setTimeout(() => { + cleanup(); + reject(new Error("Timeout chờ phản hồi từ content script")); + }, timeoutMs); + + chrome.tabs.sendMessage(tabId, message); + } else { + reject(new Error("Không tìm thấy tab Selling")); + } + } + ); + }); +} + const handleListenDeletes = () => { const evtSource = new EventSource( `${import.meta.env.VITE_API_URL}/products/delete-stream` @@ -122,6 +229,22 @@ const handleListenDeletes = () => { console.log("[SSE] Listening for delete-stream events..."); }; +const handleListenEdits = () => { + const evtSource = new EventSource( + `${import.meta.env.VITE_API_URL}/products/edit-stream` + ); + + evtSource.onmessage = (event) => { + const data = JSON.parse(event.data) as any; + console.log("[EDIT] New event:", data); + queue.add(() => handleEdits(data, 30000)); + }; + + evtSource.onerror = (err) => { + console.error("EventSource failed:", err); + }; +}; + function ensureMarketplaceSellingTab() { const targetUrl = "https://www.facebook.com/marketplace/you/selling"; @@ -144,6 +267,7 @@ function ensureMarketplaceSellingTab() { const init = async () => { handleListenPublists(); handleListenDeletes(); + handleListenEdits(); ensureMarketplaceSellingTab(); }; diff --git a/auto-listing-facebook-marketplace/src/content.ts b/auto-listing-facebook-marketplace/src/content.ts index b7ffeb0..5df37b3 100644 --- a/auto-listing-facebook-marketplace/src/content.ts +++ b/auto-listing-facebook-marketplace/src/content.ts @@ -1,521 +1,115 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ // content.ts import { productApi } from "./api/product-api.service"; -import { delay } from "./features/app"; -import axios from "./lib/axios"; -import { thiefService } from "./services/thief.service"; -const selectors = { - file__image_input: 'input[type="file"]', - title_input: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div[2]/div/div/div[5]/div/div/div/label/div/input", - price_input: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[6]/div/div/div/label/div/input", - brand_input: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[2]/div/div/div/label/div/input", - brand_input_fallback: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[2]/div/div/div/label/div/input", - description_input: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[3]/div/div/div/label/div/div/textarea", - description_input_falback: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[3]/div/div/div/label/div/div/textarea", - sku_input: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[6]/div/div/div[1]/label/div/input", - sku_input_fallback: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[6]/div/div/div[1]/label/div/input", - category_select: { - wraper: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[7]/div/div/div/div", - container: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/div/div/div/div/span/div", - }, - condition_select: { - wraper: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[8]/div/div/div/div", - container: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/div/div/div/div[1]/div", - }, - tags_input: { - input: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea", - input_falback: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea", - plus_btn: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div[2]", - }, - - location_select: { - input: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input", - input_fallback: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input", - wraper: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div", - container: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul", - container_fallback: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul", - }, - next_btn: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[5]/div/div/div", - publish_btn: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[4]/div[2]/div/div", - products: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[2]/div/div/div[2]/div[1]/div/div[2]/div/div/span/div/div", - products_fallback: - "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[2]/div/div/div[2]/div[1]/div/div[2]/div[2]/div", -}; - -const uploadImages = async (item: IItem) => { - // Tạo DataTransfer để giả lập FileList - const dt: DataTransfer = new DataTransfer(); - - for (const image of item.images) { - const base64 = await thiefService.imageUrlToBase64(image); - - console.log("Base64:", image.slice(0, 50) + "..."); - const file = thiefService.base64ToFile( - base64, - item.sku, - thiefService.getImageExtension(image) || "jpg" - ); - - dt.items.add(file); - } - - // Tìm input file của Facebook - const input: HTMLInputElement | null = document.querySelector( - selectors.file__image_input - ); - - if (input) { - // Gán file vào input - input.files = dt.files; - - // Gửi event change - input.dispatchEvent(new Event("change", { bubbles: true })); - } else { - console.error("Không tìm thấy input[type='file']"); - } -}; - -const chooseSelect = async ( - value: string, - xpaths: { wraper: string; container: string } -) => { - const el = await thiefService.getElementByXPath(xpaths.wraper); - if (!el) throw new Error("Wrapper xpath not found"); - - thiefService.scrollToElement(el); - thiefService.clickByPoint(el); - - await delay(200); - const container = await thiefService.getElementByXPath(xpaths.container); - if (!container) throw new Error("Container xpath not found"); - - // Tìm phần tử con có nội dung giống value - const matchingChild = Array.from(container.children).find((child) => - child.textContent - ?.trim() - .toLocaleLowerCase() - .replace(/–/g, "-") - .includes(value.toLocaleLowerCase()) - ) as HTMLElement | undefined; - - if (!matchingChild) throw new Error(`No child found with text "${value}"`); - - thiefService.scrollToElement(matchingChild); - - await delay(200); - thiefService.clickByPoint(matchingChild); -}; - -const chooseLocation = async ( - value: string, - { - input, - ...xpaths - }: { - wraper: string; - container: string; - container_fallback?: string; - input: string; - input_fallback?: string; - } -) => { - await thiefService.writeToInput(value, input, xpaths.input_fallback); - - await delay(200); - - const container = await thiefService.getElementByXPath(xpaths.container, { - xpathFallback: xpaths.container_fallback, - }); - if (!container) throw new Error("Container xpath not found"); - - thiefService.scrollToElement(container); - - // Tìm phần tử con có nội dung giống value - const matchingChild = Array.from(container.children).find((child) => - child.textContent - ?.trim() - .toLocaleLowerCase() - .includes(value.toLocaleLowerCase()) - ) as HTMLElement | undefined; - - if (!matchingChild) throw new Error(`No child found with text "${value}"`); - - thiefService.scrollToElement(matchingChild); - - await delay(200); - thiefService.clickByPoint(matchingChild); -}; - -const writeTags = async ( - tags: string[], - xpaths: { input: string; input_falback?: string; plus_btn: string } -) => { - const input = await thiefService.getElementByXPath(xpaths.input, { - xpathFallback: xpaths?.input_falback, - }); - - if (!input) throw new Error("Input is not found"); - - thiefService.scrollToElement(input); - - await delay(200); - for (const tag of tags) { - await thiefService.writeToInput(tag, xpaths.input, xpaths?.input_falback); - - await delay(200); - - thiefService.pressEnter(input); - } -}; - -const finistPublist = async ( - item: IItem, - values: { error?: string; published: boolean } -) => { - const { data } = await axios({ - url: "products/publist-finish/" + item.id, - method: "POST", - data: values, - }); - - return data; -}; - -const finistDelete = async ( - item: IItem, - values: { error?: string; published: boolean } -) => { - const { data } = await axios({ - url: "products/delete-finish/" + item.id, - method: "POST", - data: values, - }); - - return data; -}; - -const clickNext = async () => { - const btn = await thiefService.getElementByXPath(selectors.next_btn); - - if (!btn) throw new Error("Next button is not found"); - - thiefService.clickByPoint(btn); -}; - -const clickPublist = async () => { - const btn = await thiefService.getElementByXPath(selectors.publish_btn); - - if (!btn) throw new Error("Publist button is not found"); - - thiefService.clickByPoint(btn); -}; - -/** - * B1. Upload images - * B2. Write title - * B3. Write price - * B4. Select category - * B5. Select condition - * - */ -const handle = async (item: IItem) => { - console.log({ item }); - - await delay(1000); - // B1. Upload images - await uploadImages(item); - - await delay(200); - // B2. Write title - thiefService.writeToInput(item.title, selectors.title_input); - - await delay(200); - // B3. Write price - thiefService.writeToInput(String(item.price), selectors.price_input); - - await delay(200); - // B4. Select category - await chooseSelect(item.category, selectors.category_select); - - await delay(200); - // B5. Select condition - await chooseSelect(item.condition, selectors.condition_select); - - if (item.brand) { - await delay(200); - // B6. Write brand - await thiefService.writeToInput( - item.brand, - selectors.brand_input, - selectors.brand_input_fallback - ); - } - - await delay(200); - // B7. Write description - await thiefService.writeToInput( - item.description, - selectors.description_input, - selectors.description_input_falback - ); - - await delay(200); - - await writeTags(item.tags, selectors.tags_input); - - await delay(200); - // B8. Write sku - await thiefService.writeToInput( - item.sku, - selectors.sku_input, - selectors.sku_input_fallback - ); - - if (item?.location) { - await delay(200); - - await chooseLocation(item.location, selectors.location_select); - } - - await delay(200); - - await clickNext(); - - await delay(200); - - await clickPublist(); - - return true; -}; - -// function extractListings(productsEl: HTMLElement) { -// const children = Array.from(productsEl.children); - -// return children.map((child) => { -// // Lấy title (thường là span hoặc div có dir="auto") -// const titleEl = child.querySelector('span[dir="auto"], div[dir="auto"]'); -// const title = titleEl?.textContent?.trim() || ""; - -// // Lấy giá (span có attribute dir="auto") -// const priceEl = Array.from(child.querySelectorAll('span[dir="auto"]')).find( -// (el) => -// /\d/.test(el.textContent || "") && /[AU$]/.test(el.textContent || "") -// ); - -// // Tách lấy số, ví dụ: "AU$20" -> "20" -// const priceMatch = priceEl?.textContent?.match(/\d+(?:\.\d+)?/); -// const price = priceMatch ? parseFloat(priceMatch[0]) : 0; - -// return { title, price, el: productsEl }; -// }); -// } - -// const getProducts = async () => { -// let products = await thiefService.getElementByXPath(selectors.products); -// if (!products) { -// products = await thiefService.getElementByXPath( -// selectors.products_fallback -// ); -// } - -// if (!products) return []; - -// return extractListings(products) as ISyncItem[]; -// }; - -const getProducts = async () => { - const products1 = await thiefService.getElementByXPath(selectors.products); - const products2 = await thiefService.getElementByXPath( - selectors.products_fallback - ); - - // Gom 2 cái vào một mảng, bỏ null - const allProductsEls = [products1, products2].filter( - Boolean - ) as HTMLElement[]; - - if (allProductsEls.length === 0) return []; - - // Nối tất cả kết quả extractListings từ mỗi element - return allProductsEls.flatMap((el) => extractListings(el)) as ISyncItem[]; -}; - -function extractListings(productsEl: HTMLElement) { - const children = Array.from(productsEl.children); - - return children.map((child) => { - // Lấy title - const titleEl = child.querySelector('span[dir="auto"], div[dir="auto"]'); - const title = titleEl?.textContent?.trim() || ""; - - // Lấy giá - const priceEl = Array.from(child.querySelectorAll('span[dir="auto"]')).find( - (el) => - /\d/.test(el.textContent || "") && /[AU$]/.test(el.textContent || "") - ); - - const priceMatch = priceEl?.textContent?.match(/[\d,]+(?:\.\d+)?/); - const price = priceMatch ? parseFloat(priceMatch[0].replace(/,/g, "")) : 0; - - return { title, price, el: productsEl }; - }); -} - -const syncListing = async () => { - const url = window.location.href; - if (!url.includes("https://www.facebook.com/marketplace/you/selling")) return; - - const products = await getProducts(); - - if (!products.length) return; - - const response = await productApi.sync( - products.map((item) => ({ - title: item.title, - price: item.price, - })) as ISyncItem[] - ); - - console.log({ response }); -}; - -const closeTab = async (data: IItem) => { - chrome.runtime.sendMessage({ - type: "close-tab", - payload: data, - }); -}; - -const handleDelete = async (payload: IItem) => { - const products = await getProducts(); - - const product = products.find((product) => { - return product.title == payload.title && product.price == payload.price; - }); - - console.log({ payload, product, products }); - - if (!product) return; - - const el = product.el; - - const optionEl = el.querySelector( - `[aria-label="More options for ${product.title}"]` - ); - - console.log({ optionEl }); - if (!optionEl) return; - - (optionEl as any).click?.(); - - await delay(2000); - - const items = Array.from(document.querySelectorAll('[role="menuitem"]')); - - console.log({ items }); - const deleteItem = items.find((item) => - item.textContent.toLocaleLowerCase().includes("delete") - ); - - (deleteItem as any).click?.(); - - await delay(1000); - - const btnDelete = await thiefService.getElementByXPath( - "/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div/div/div/div[3]/div/div/div/div/div[1]/div", - { - xpathFallback: - "/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div[3]/div[2]/div/div[2]/div[1]", - } - ); - - console.log({ btnDelete }); - - btnDelete?.click(); - - const closeModal = await thiefService.getElementByXPath( - "/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div[2]/div" - ); - - closeModal?.click(); - - await finistDelete(payload, { published: false }); - - chrome.runtime.sendMessage({ type: "delete-done" }); -}; +import { delay, delayRD } from "./features/app"; +import { facebookService } from "./services/facebook.service"; const port = chrome.runtime.connect(); +// Listent event to get payload publist port.onMessage.addListener(async (message) => { - if (message.type === "publist-event") { + if (message.type === "PUBLIST_EVENT") { const data = message.payload as IItem; if (!data) return; - console.log("Received new product event:", data); - - // data.images = ["images/1.png", "images/2.png"]; + console.log("[PUBLIST_EVENT] Received new product event:", data); try { - await delay(500); - await handle(data); + await delayRD(500, 600); + await facebookService.handlePublist(data); } catch (error) { - await finistPublist(data, { + await productApi.finistPublist(data, { error: (error as { message: string }).message, published: false, }); } finally { - await finistPublist(data, { published: true }); + await productApi.finistPublist(data, { published: true }); await delay(5000); - await closeTab(data); + await facebookService.closeTab(data); } } }); -// content.js -chrome.runtime.onMessage.addListener((message) => { - if (message.type === "DELETE_STREAM_DATA") { - console.log("Nhận dữ liệu từ background:", message.payload); +// Listent event to get payload edit +port.onMessage.addListener(async (message) => { + if (message.type === "EDIT_EVENT") { + const { data, prev } = message.payload as { prev: IItem; data: IItem }; - handleDelete(message.payload); + if (!data) return; + + console.log("[PUBLIST_EVENT] Received new product event:", data); + + try { + await facebookService.handleUpdate(data); + } catch (error) { + await productApi.updatePublist(data, { + error: (error as { message: string }).message, + published: true, + publist_id: prev.publist_id, + }); + } finally { + await delay(3000); + + await productApi.updatePublist(data, { + published: true, + message: `Edited product ${data.title}`, + publist_id: prev.publist_id, + }); + + await facebookService.closeTab(data); + } } }); +// Listent event to delete +chrome.runtime.onMessage.addListener(async (message) => { + if (message.type === "DELETE_STREAM_DATA") { + const data = message.payload as IItem; + + if (!data) return; + + console.log("[DELETE_STREAM_DATA] Received new product event:", data); + + try { + await facebookService.handleDelete(message.payload); + } catch (error) { + await productApi.finistPublist(data, { + error: (error as { message: string }).message, + published: false, + }); + } + } +}); + +// Listent event to get publist id +chrome.runtime.onMessage.addListener(async (message) => { + if (message.type === "GET_PUBLIST_ID") { + console.log({ message }); + const { + data: { prev }, + } = message; + + const publist_id = await facebookService.handleGetPublistID(prev); + + // Gửi message đến background/content script thông báo là get đã xong + chrome.runtime.sendMessage({ type: "GET_PUBLIST_ID_DONE", publist_id }); + } + return true; // Để cho phép async sendResponse +}); + async function init() { - // const { data } = await axios.get("products/33"); + // const { data } = await axios.get("products/53"); // if (!data.data) return; // const item = data.data as IItem; - // await handle(item); + // await facebookService.handleUpdate(item); - await syncListing(); + await facebookService.syncListing(); } init(); diff --git a/auto-listing-facebook-marketplace/src/features/app.ts b/auto-listing-facebook-marketplace/src/features/app.ts index 3f75b45..ef325f8 100644 --- a/auto-listing-facebook-marketplace/src/features/app.ts +++ b/auto-listing-facebook-marketplace/src/features/app.ts @@ -16,7 +16,7 @@ export function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -export function randomDelay(minMs: number, maxMs: number): Promise { +export function delayRD(minMs: number, maxMs: number): Promise { const time = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs; return delay(time); } diff --git a/auto-listing-facebook-marketplace/src/interfate.d.ts b/auto-listing-facebook-marketplace/src/interfate.d.ts index cf0b88f..1e43815 100644 --- a/auto-listing-facebook-marketplace/src/interfate.d.ts +++ b/auto-listing-facebook-marketplace/src/interfate.d.ts @@ -11,6 +11,7 @@ interface IItem { sku: string; location?: string; id: number; + publist_id?: string; } interface ISyncItem { diff --git a/auto-listing-facebook-marketplace/src/services/facebook.service.ts b/auto-listing-facebook-marketplace/src/services/facebook.service.ts new file mode 100644 index 0000000..d40c189 --- /dev/null +++ b/auto-listing-facebook-marketplace/src/services/facebook.service.ts @@ -0,0 +1,630 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { delay, delayRD } from "@/features/app"; +import { thiefService } from "./thief.service"; +import { productApi } from "@/api/product-api.service"; + +class FacebookService { + sellingPath = "https://www.facebook.com/marketplace/you/selling"; + + selectors = { + file__image_input: 'input[type="file"]', + title_input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div[2]/div/div/div[5]/div/div/div/label/div/input", + price_input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[6]/div/div/div/label/div/input", + brand_input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[2]/div/div/div/label/div/input", + brand_input_fallback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[2]/div/div/div/label/div/input", + description_input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[3]/div/div/div/label/div/div/textarea", + description_input_falback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[3]/div/div/div/label/div/div/textarea", + sku_input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[6]/div/div/div[1]/label/div/input", + sku_input_fallback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[6]/div/div/div[1]/label/div/input", + category_select: { + wraper: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[7]/div/div/div/div", + container: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/div/div/div/div/span/div", + }, + condition_select: { + wraper: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[8]/div/div/div/div", + container: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/div/div/div/div[1]/div", + }, + tags_input: { + input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea", + input_falback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea", + plus_btn: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div[2]", + }, + tags_edit_input: { + input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[4]/div/div/div/div[1]/label/div/div/div[2]/div[1]/textarea", + input_falback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div/textarea", + plus_btn: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div/div[1]/label/div/div/div[2]/div[2]", + }, + + location_select: { + input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input", + input_fallback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input", + wraper: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div", + container: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul", + container_fallback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul", + }, + location_edit_select: { + input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[6]/div/div/div/div/div/div/div/div/label/div[2]/input", + input_fallback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[10]/div/div/div[7]/div/div/div/div/div/div/div/div/label/div[2]/input", + wraper: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[7]/div/div/div/div/div/div/div/div", + container: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul", + container_fallback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[2]/div/div/div[1]/div[1]/div/ul", + }, + next_btn: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[5]/div/div/div", + update_btn: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[4]/div/div/div", + publish_btn: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[4]/div[2]/div/div", + products: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[2]/div/div/div[2]/div[1]/div/div[2]/div/div/span/div/div", + products_fallback: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[2]/div/div/div[2]/div[1]/div/div[2]/div[2]/div", + option_btn: + "/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div/div/div/div[3]/div/div/div/div/div[1]/div", + option_btn_fallback: + "/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div[3]/div[2]/div/div[2]/div[1]", + close_btn_modal_feedback: + "/html/body/div[1]/div/div[1]/div/div[4]/div/div/div[1]/div/div[2]/div/div/div/div[2]/div", + images_container: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div[2]/div/div/div[3]/div[2]/div", + description_edit_input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[2]/div/div/div/label/div/div/textarea", + sku_edit_input: + "/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div[3]/div[1]/div[2]/div/div/div[9]/div/div/div[5]/div/div/div[1]/label/div/input", + }; + + clearImages = async () => { + const el = await thiefService.getElementByXPath( + this.selectors.images_container + ); + + if (!el) throw new Error(`Can't not found xpath images_container`); + + const children = Array.from(el.children) as HTMLElement[]; + console.log(children); + + // Ví dụ: clear hết + children.forEach((child) => + (child.querySelector('[aria-label="Remove"]') as any)?.click() + ); + }; + + uploadImages = async (item: IItem) => { + // Tạo DataTransfer để giả lập FileList + const dt: DataTransfer = new DataTransfer(); + + for (const image of item.images) { + const base64 = await thiefService.imageUrlToBase64(image); + + console.log("Base64:", image.slice(0, 50) + "..."); + const file = thiefService.base64ToFile( + base64, + item.sku, + thiefService.getImageExtension(image) || "jpg" + ); + + dt.items.add(file); + } + + // Tìm input file của Facebook + const input: HTMLInputElement | null = document.querySelector( + this.selectors.file__image_input + ); + + if (input) { + // Gán file vào input + input.files = dt.files; + + // Gửi event change + input.dispatchEvent(new Event("change", { bubbles: true })); + } else { + console.error("Không tìm thấy input[type='file']"); + } + }; + + chooseSelect = async ( + value: string, + xpaths: { wraper: string; container: string } + ) => { + const el = await thiefService.getElementByXPath(xpaths.wraper); + if (!el) throw new Error("Wrapper xpath not found"); + + thiefService.scrollToElement(el); + thiefService.clickByPoint(el); + + await delayRD(300, 500); + + const container = await thiefService.getElementByXPath(xpaths.container); + if (!container) throw new Error("Container xpath not found"); + + // Tìm phần tử con có nội dung giống value + const matchingChild = Array.from(container.children).find((child) => + child.textContent + ?.trim() + .toLocaleLowerCase() + .replace(/–/g, "-") + .includes(value.toLocaleLowerCase()) + ) as HTMLElement | undefined; + + if (!matchingChild) throw new Error(`No child found with text "${value}"`); + + thiefService.scrollToElement(matchingChild); + + await delay(200); + thiefService.clickByPoint(matchingChild); + }; + + chooseLocation = async ( + value: string, + { + input, + ...xpaths + }: { + wraper: string; + container: string; + container_fallback?: string; + input: string; + input_fallback?: string; + } + ) => { + await thiefService.writeToInput(value, input, xpaths.input_fallback); + + await delay(200); + + const container = await thiefService.getElementByXPath(xpaths.container, { + xpathFallback: xpaths.container_fallback, + }); + if (!container) throw new Error("Container xpath not found"); + + thiefService.scrollToElement(container); + + // Tìm phần tử con có nội dung giống value + const matchingChild = Array.from(container.children).find((child) => + child.textContent + ?.trim() + .toLocaleLowerCase() + .includes(value.toLocaleLowerCase()) + ) as HTMLElement | undefined; + + if (!matchingChild) throw new Error(`No child found with text "${value}"`); + + thiefService.scrollToElement(matchingChild); + + await delay(200); + thiefService.clickByPoint(matchingChild); + }; + + writeTags = async ( + tags: string[], + xpaths: { input: string; input_falback?: string; plus_btn: string } + ) => { + const input = await thiefService.getElementByXPath(xpaths.input, { + xpathFallback: xpaths?.input_falback, + }); + + if (!input) throw new Error("Input is not found"); + + thiefService.scrollToElement(input); + + await delay(200); + for (const tag of tags) { + await thiefService.writeToInput(tag, xpaths.input, xpaths?.input_falback); + + await delay(200); + + thiefService.pressEnter(input); + } + }; + + clickNext = async () => { + const btn = await thiefService.getElementByXPath(this.selectors.next_btn); + + if (!btn) throw new Error("Next button is not found"); + + thiefService.clickByPoint(btn); + }; + + clickUpdate = async () => { + const btn = await thiefService.getElementByXPath(this.selectors.update_btn); + + if (!btn) throw new Error("Next button is not found"); + + thiefService.clickByPoint(btn); + }; + + clickPublist = async () => { + const btn = await thiefService.getElementByXPath( + this.selectors.publish_btn + ); + + if (!btn) throw new Error("Publist button is not found"); + + thiefService.clickByPoint(btn); + }; + + /** + * B1. Upload images + * B2. Write title + * B3. Write price + * B4. Select category + * B5. Select condition + * ..... + */ + handlePublist = async (item: IItem) => { + console.log({ item }); + + await delayRD(1000, 2000); + // B1. Upload images + await this.uploadImages(item); + + await delayRD(300, 500); + // B2. Write title + thiefService.writeToInput(item.title, this.selectors.title_input); + + await delayRD(300, 500); + + // B3. Write price + thiefService.writeToInput(String(item.price), this.selectors.price_input); + + await delayRD(300, 500); + + // B4. Select category + await this.chooseSelect(item.category, this.selectors.category_select); + + await delayRD(300, 500); + + // B5. Select condition + await this.chooseSelect(item.condition, this.selectors.condition_select); + + if (item.brand) { + await delayRD(300, 500); + + // B6. Write brand + await thiefService.writeToInput( + item.brand, + this.selectors.brand_input, + this.selectors.brand_input_fallback + ); + } + + await delayRD(300, 500); + + // B7. Write description + await thiefService.writeToInput( + item.description, + this.selectors.description_input, + this.selectors.description_input_falback + ); + + await delayRD(300, 500); + + await this.writeTags(item.tags, this.selectors.tags_input); + + await delayRD(300, 500); + + // B8. Write sku + await thiefService.writeToInput( + item.sku, + this.selectors.sku_input, + this.selectors.sku_input_fallback + ); + + if (item?.location) { + await delayRD(300, 500); + + await this.chooseLocation(item.location, this.selectors.location_select); + } + + await delayRD(300, 500); + + await this.clickNext(); + + await delayRD(300, 500); + + await this.clickPublist(); + + return true; + }; + + /** + * B1. Upload images + * B2. Write title + * B3. Write price + * B4. Select category + * B5. Select condition + * ..... + */ + handleUpdate = async (item: IItem) => { + console.log({ item }); + + await delayRD(1000, 2000); + + await this.clearImages(); + + await delayRD(1000, 2000); + + await this.uploadImages(item); + + await delayRD(1000, 2000); + + thiefService.writeToInput(item.title, this.selectors.title_input); + + await delayRD(300, 500); + + thiefService.writeToInput(String(item.price), this.selectors.price_input); + + await delayRD(300, 500); + + await this.chooseSelect(item.category, this.selectors.category_select); + + await delayRD(300, 500); + + await this.chooseSelect(item.condition, this.selectors.condition_select); + + await delayRD(300, 500); + + await thiefService.writeToInput( + item.description, + this.selectors.description_edit_input + ); + + await delayRD(300, 500); + + await this.writeTags(item.tags, this.selectors.tags_edit_input); + + await delayRD(300, 500); + + await thiefService.writeToInput(item.sku, this.selectors.sku_edit_input); + + if (item?.location) { + await delayRD(300, 500); + + await this.chooseLocation( + item.location, + this.selectors.location_edit_select + ); + } + + await delayRD(300, 500); + + await this.clickUpdate(); + + return true; + }; + + getProducts = async () => { + const products1 = await thiefService.getElementByXPath( + this.selectors.products + ); + const products2 = await thiefService.getElementByXPath( + this.selectors.products_fallback + ); + + // Gom 2 cái vào một mảng, bỏ null + const allProductsEls = [products1, products2].filter( + Boolean + ) as HTMLElement[]; + + if (allProductsEls.length === 0) return []; + + // Nối tất cả kết quả extractListings từ mỗi element + return allProductsEls.flatMap((el) => + this.extractListings(el) + ) as ISyncItem[]; + }; + + extractListings(productsEl: HTMLElement) { + const children = Array.from(productsEl.children); + + return children.map((child) => { + // Lấy title + const titleEl = child.querySelector('span[dir="auto"], div[dir="auto"]'); + const title = titleEl?.textContent?.trim() || ""; + + // Lấy giá + const priceEl = Array.from( + child.querySelectorAll('span[dir="auto"]') + ).find( + (el) => + /\d/.test(el.textContent || "") && /[AU$]/.test(el.textContent || "") + ); + + const priceMatch = priceEl?.textContent?.match(/[\d,]+(?:\.\d+)?/); + const price = priceMatch + ? parseFloat(priceMatch[0].replace(/,/g, "")) + : 0; + + return { title, price, el: productsEl }; + }); + } + + closeTab = async (data: IItem) => { + chrome.runtime.sendMessage({ + type: "close-tab", + payload: data, + }); + }; + + clickOptionOfProduct(el: HTMLElement, data: IItem) { + // Tìm nút "More options" cho sản phẩm đó + const optionEl = el.querySelector( + `[aria-label="More options for ${data.title}"]` + ); + + if (!optionEl) + throw new Error( + `Not found option buttin in product ${data.title}, ID: ${data.id}` + ); + + // Click vào nút "More options" để mở menu + (optionEl as any).click?.(); + } + + async getOptionEls(el: HTMLElement, data: IItem) { + this.clickOptionOfProduct(el, data); + + // Delay 2 giây để menu hiển thị hoàn toàn + await delay(2000); + + // Lấy tất cả các item trong menu (thường là 'Edit', 'Delete', ...) + const items = Array.from(document.querySelectorAll('[role="menuitem"]')); + + return items; + } + + clickItemInList(items: Element[], innerText: string) { + // Tìm item chứa chữ "delete" + const item = items.find((item) => + item.textContent.toLocaleLowerCase().includes(innerText) + ); + + if (!item) throw new Error(`Not found item ${innerText} in options list`); + + // Click vào item "Delete" + (item as any).click?.(); + } + + getItemInList(items: Element[], innerText: string) { + // Tìm item chứa chữ "delete" + const item = items.find((item) => + item.textContent.toLocaleLowerCase().includes(innerText) + ); + + if (!item) throw new Error(`Not found item ${innerText} in options list`); + + return item; + } + + handleDelete = async (payload: IItem) => { + // Lấy tất cả sản phẩm hiện tại trên Facebook Marketplace + const products = await facebookService.getProducts(); + + // Tìm sản phẩm khớp với title và price từ payload + const product = products.find((product) => { + return product.title == payload.title && product.price == payload.price; + }); + + console.log({ payload, product, products }); + + // Nếu không tìm thấy sản phẩm nào khớp thì kết thúc + if (!product) return; + + const el = product.el; // element HTML đại diện cho sản phẩm trên trang + + // Lấy tất cả các item trong menu (thường là 'Edit', 'Delete', ...) + const items = await this.getOptionEls(el, payload); + + console.log({ items }); + + // // Tìm item chứa chữ "delete" + this.clickItemInList(items, "delete"); + + // Delay 1 giây để modal xác nhận xóa hiện ra + await delay(1000); + + // Lấy nút "Options" trong modal (hoặc fallback nếu xpath chính không tìm thấy) + const confirmBtn = await thiefService.getElementByXPath( + this.selectors.option_btn, + { + xpathFallback: this.selectors.option_btn_fallback, + } + ); + + console.log({ confirmBtn }); + + // Click vào nút "Options" trong modal nếu tìm thấy + confirmBtn?.click(); + + // Lấy nút "Close" của modal feedback (nếu có) để đóng modal sau khi xóa + const closeBtnModalFeedback = await thiefService.getElementByXPath( + this.selectors.close_btn_modal_feedback + ); + + closeBtnModalFeedback?.click(); + + // Gọi API backend để đánh dấu sản phẩm đã xóa (published = false) + await productApi.finistDelete(payload, { published: false }); + + // Gửi message đến background/content script thông báo là delete đã xong + chrome.runtime.sendMessage({ type: "delete-done" }); + }; + + syncListing = async () => { + const url = window.location.href; + if (!url.includes(this.sellingPath)) return; + + const products = await facebookService.getProducts(); + + const response = await productApi.sync( + products.map((item) => ({ + title: item.title, + price: item.price, + })) as ISyncItem[] + ); + + console.log({ response }); + }; + + extractMarketplaceItemId(url: string): string | null { + const match = url.match(/\/marketplace\/item\/(\d+)/); + return match ? match[1] : null; + } + + handleGetPublistID = async (payload: IItem) => { + // Lấy tất cả sản phẩm hiện tại trên Facebook Marketplace + const products = await facebookService.getProducts(); + + // Tìm sản phẩm khớp với title và price từ payload + const product = products.find((product) => { + return product.title == payload.title && product.price == payload.price; + }); + + console.log({ payload, product, products }); + + // Nếu không tìm thấy sản phẩm nào khớp thì kết thúc + if (!product) return; + + const el = product.el; // element HTML đại diện cho sản phẩm trên trang + + // Lấy tất cả các item trong menu (thường là 'Edit', 'Delete', ...) + const items = await this.getOptionEls(el, payload); + + const item = this.getItemInList(items, "view listing"); + + const publistID = this.extractMarketplaceItemId((item as any)["href"]); + + this.clickOptionOfProduct(el, payload); + + return publistID; + }; +} + +export const facebookService = new FacebookService(); diff --git a/client/.env b/client/.env index aafaa20..45a6d2f 100644 --- a/client/.env +++ b/client/.env @@ -1,3 +1,3 @@ VITE_APP_NAME = 'Admin' -VITE_BASE_URL = 'http://localhost:4000/api/v1/' \ No newline at end of file +VITE_BASE_URL = 'http://10.20.2.227:4000/api/v1/' \ No newline at end of file diff --git a/client/app/api/core-api-service.ts b/client/app/api/core-api-service.ts index 0342503..223f9fe 100644 --- a/client/app/api/core-api-service.ts +++ b/client/app/api/core-api-service.ts @@ -34,36 +34,36 @@ export class BaseApiService { return response.data; } - async create(data: Partial>) { + async create(values: Partial>) { try { - const newData = removeUndefinedValues(data); - const { data: result } = await axios({ + const newData = removeUndefinedValues(values); + const { data } = await axios({ url: this.resourceUrl, // withCredentials: true, method: "POST", data: newData, }); - handleSuccess(result, this.resourceUrl); - return result; + handleSuccess(data, this.resourceUrl); + return data; } catch (error) { handleError(error); } } - async update(id: T["id"], data: Partial) { + async update(id: T["id"], values: Partial) { try { - const cleaned = removeUndefinedValues(data); - const { data: result } = await axios({ + const cleaned = removeUndefinedValues(values); + const { data } = await axios({ url: `${this.resourceUrl}/${id}`, // withCredentials: true, method: "PUT", data: cleaned, }); - handleSuccess(result, this.resourceUrl); + handleSuccess(data, this.resourceUrl); - return result; + return data; } catch (error) { handleError(error); } diff --git a/client/app/components/btn/confirm-alert.tsx b/client/app/components/btn/confirm-alert.tsx index 98a59d4..1f09c6a 100644 --- a/client/app/components/btn/confirm-alert.tsx +++ b/client/app/components/btn/confirm-alert.tsx @@ -16,8 +16,8 @@ import { Button } from "../ui/button"; export function ConfirmAlert({ children, - title = "Bạn có chắc không?", - description = "Hành động này không thể hoàn tác.", + title = "Are you sure ?", + description = "This action cannot be undone.", onConfirm, }: { children: ReactNode; @@ -36,7 +36,7 @@ export function ConfirmAlert({ {description} - Hủy + Cancel diff --git a/client/app/components/core/data-table.tsx b/client/app/components/core/data-table.tsx index 46f1400..c1ca096 100644 --- a/client/app/components/core/data-table.tsx +++ b/client/app/components/core/data-table.tsx @@ -28,6 +28,8 @@ import { X, } from "lucide-react"; import { + cloneElement, + isValidElement, useCallback, useEffect, useMemo, @@ -224,7 +226,7 @@ export interface DataTableProps { pageSize?: number; onPageSizeChange?: (newPageSize: number) => void; onRowClick?: (row: T) => void; - onEdit?: (row: T) => void; + onEdit?: (row: T, children?: ReactNode) => void | ReactNode; onDelete?: (row: T) => void; onView?: (row: T) => void; selectable?: boolean; @@ -1448,16 +1450,28 @@ export function DataTable>({ Xem )} - {onEdit && ( - onEdit(row)} - className="cursor-pointer" - > - - Sửa - - )} + {onEdit && + (() => { + const content = ( + { + e.preventDefault(); // Ngăn dropdown đóng lại + e.stopPropagation(); + }} + onClick={(e) => onEdit(row)} + className="cursor-pointer" + > + + Edit + + ); + const wrapped = onEdit(row, content); + + return isValidElement(wrapped) + ? cloneElement(wrapped, {}, content) + : content; + })()} {/* Custom actions */} {visibleCustomActions.map((action) => ( (obj: T): Partial { return _.pickBy(obj, Boolean); } diff --git a/client/app/routes/products/components/modals/product-modal.tsx b/client/app/routes/products/components/modals/product-modal.tsx index 9bff7d5..bffc9a7 100644 --- a/client/app/routes/products/components/modals/product-modal.tsx +++ b/client/app/routes/products/components/modals/product-modal.tsx @@ -1,11 +1,13 @@ "use client"; -import { useState, type ReactNode } from "react"; -import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Plus, X, Upload, Link, ImageIcon } from "lucide-react"; +import { ImageIcon, Link, Plus, Upload, X } from "lucide-react"; +import { useEffect, useState, type ReactNode } from "react"; +import { useForm } from "react-hook-form"; +import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; +import { Card, CardContent } from "~/components/ui/card"; import { Dialog, DialogContent, @@ -14,7 +16,6 @@ import { DialogTrigger, } from "~/components/ui/dialog"; import { Input } from "~/components/ui/input"; -import { Textarea } from "~/components/ui/textarea"; import { Select, SelectContent, @@ -22,21 +23,28 @@ import { SelectTrigger, SelectValue, } from "~/components/ui/select"; -import { Badge } from "~/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; -import { Card, CardContent } from "~/components/ui/card"; +import { Textarea } from "~/components/ui/textarea"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import axios from "axios"; +import z from "zod/v3"; +import { productApi } from "~/api/products-api.service"; +import { ConfirmAlert } from "~/components/btn/confirm-alert"; +import Loader from "~/components/loader"; import { Form, + FormControl, FormField, FormItem, FormLabel, - FormControl, FormMessage, } from "~/components/ui/form"; -import z from "zod/v3"; import { urlToBase64 } from "~/features/base64"; -import { productApi } from "~/api/products-api.service"; +import { delay } from "~/features/delay"; +import { cn } from "~/lib/utils"; +import { Checkbox } from "~/components/ui/checkbox"; +import { Label } from "~/components/ui/label"; export const productSchema = z.object({ images: z.array(z.string()).min(1, "At least 1 image is required"), @@ -63,16 +71,19 @@ export const productSchema = z.object({ tags: z.array(z.string()).optional(), sku: z.string().min(1, "Sku must be required"), location: z.string().optional(), + publist: z.boolean().optional(), }); export type ProductFormData = z.infer; export default function ProductModal({ children, + data, ...props }: { children: ReactNode; onSubmit?: () => void; + data?: IProduct; }) { const [open, setOpen] = useState(false); const [tagInput, setTagInput] = useState(""); @@ -91,6 +102,7 @@ export default function ProductModal({ tags: [], sku: "", location: "", + publist: false, }, }); @@ -100,6 +112,74 @@ export default function ProductModal({ const conditions = ["New", "Used - like new", "Used - good", "Used - fair"]; const categories = ["Tools"]; + const { isLoading, refetch, ...query } = useQuery({ + queryKey: ["product", data?.id], + queryFn: async () => { + if (!data) return null; + + await delay(300); // Giả lập delay để thấy loading + const res = await productApi.get(data.id); + return res; + }, + enabled: false, + }); + + const delImageMutation = useMutation({ + mutationFn: async (imageUrl: string) => { + await delay(300); + + return await axios.delete(imageUrl); + }, + onSuccess: (data) => { + refetch(); + }, + }); + + const actionMutation = useMutation({ + mutationFn: async (formData: ProductFormData & { id?: number }) => { + const { id, brand, publist, ...rest } = formData; + let response; + + if (id) { + // Update + response = await productApi.update(id, { ...rest, id }); + + if (publist && response?.data) { + const action = data?.status ? "re-publist" : "publist"; + await productApi.customAction( + response.data.id, + action, + response.data + ); + } + } else { + // Create + response = await productApi.create({ ...rest, brand }); + + if (publist && response?.data) { + await productApi.customAction( + response.data.id, + "publist", + response.data + ); + } + } + + return response; + }, + onSuccess: () => { + refetch(); // làm mới danh sách + setOpen(false); + setUrlInput(""); + setTagInput(""); + props.onSubmit?.(); + form.reset(); + }, + onError: (error) => { + console.error("Mutation failed:", error); + }, + }); + const handleImageUpload = (e: React.ChangeEvent) => { const files = e.target.files; if (files) { @@ -123,12 +203,17 @@ export default function ProductModal({ } }; - const removeImage = (index: number) => { + const removeImage = (index: number, image: string) => { const currentImages = form.getValues("images"); - form.setValue( - "images", - currentImages.filter((_, i) => i !== index) - ); + + if (data && isHttpUrl(image)) { + delImageMutation.mutate(image); + } else { + form.setValue( + "images", + currentImages.filter((_, i) => i !== index) + ); + } }; const addTag = () => { @@ -147,41 +232,62 @@ export default function ProductModal({ ); }; - const onSubmit = async (data: ProductFormData) => { + const onSubmit = async ({ images, ...values }: ProductFormData) => { try { - const images = data.images; + let imagesToConvert: string[] = []; + + if (data) { + // Có data => đang update + const oldImages = data.images || []; + const newImages = images || []; + + // Lấy ra hình khác so với data.images + imagesToConvert = newImages.filter((img) => !oldImages.includes(img)); + } else { + // Không có data => đang create + imagesToConvert = images || []; + } + + // Convert blob url sang base64 nếu cần const convertedImages = await Promise.all( - images.map(async (img) => { - if (img.startsWith("blob:")) { - // convert blob url to base64 - return await urlToBase64(img); - } - return img; // giữ nguyên nếu ko phải blob url - }) + imagesToConvert.map(async (img) => + img.startsWith("blob:") ? await urlToBase64(img) : img + ) ); - const dataToSubmit = { - ...data, + const dataToSubmit: ProductFormData = { + ...values, images: convertedImages, }; console.log("Product data to submit:", dataToSubmit); - const response = await productApi.create(dataToSubmit); + // const response = data + // ? await (async () => { + // const { brand, ...d } = dataToSubmit; + // return await productApi.update(data.id, { ...d, id: data.id }); + // })() + // : await productApi.create(dataToSubmit); - if (!response) return; + // if (!response) return; - setOpen(false); - form.reset(); - setUrlInput(""); - setTagInput(""); + actionMutation.mutate({ ...dataToSubmit, id: data?.id }); - props.onSubmit?.(); + console.log("Hình mới cần xử lý:", convertedImages); } catch (error) { console.error("Error submitting form:", error); } }; + const isHttpUrl = (url: string) => { + try { + const u = new URL(url); + return u.protocol === "http:" || u.protocol === "https:"; + } catch { + return false; + } + }; + const handleClose = () => { setOpen(false); form.reset(); @@ -189,6 +295,18 @@ export default function ProductModal({ setTagInput(""); }; + useEffect(() => { + if (data) { + form.reset(data); + } + }, [data]); + + useEffect(() => { + if (query.isSuccess && query.isFetched && query.data?.data) { + form.reset(query.data?.data); + } + }, [delImageMutation.isSuccess, query.isFetched, query.isSuccess]); + return ( {children} @@ -196,7 +314,7 @@ export default function ProductModal({ {/* Header */} - Create new product + {data ? "Edit product" : "Create new product"} @@ -292,15 +410,40 @@ export default function ProductModal({ alt={`Preview ${index + 1}`} className="w-full h-32 object-cover rounded-lg border" /> - + {data && isHttpUrl(image) ? ( + removeImage(index, image)} + > + + + ) : ( + + )} ))} @@ -415,7 +558,11 @@ export default function ProductModal({ Brand - + @@ -469,15 +616,13 @@ export default function ProductModal({
{watchedTags.map((tag) => ( removeTag(tag)} key={tag} variant="secondary" className="flex items-center gap-1 px-3 py-1" > {tag} - removeTag(tag)} - /> + ))}
@@ -492,6 +637,7 @@ export default function ProductModal({ Sku * @@ -517,6 +663,41 @@ export default function ProductModal({ )} /> + { + const isUpdate = !!data?.id; // đang update nếu có id + const canRepublish = isUpdate && data?.status === true; + + return ( + + +
+ +
+ +

+ {canRepublish + ? "Make this product visible again after updating" + : "Automatically publish this product when saving"} +

+
+
+
+ +
+ ); + }} + /> @@ -533,7 +714,13 @@ export default function ProductModal({ onClick={form.handleSubmit(onSubmit)} disabled={form.formState.isSubmitting} > - {form.formState.isSubmitting ? "Đang thêm..." : "Create"} + {form.formState.isSubmitting ? ( + + ) : data ? ( + "Save" + ) : ( + "Create" + )} diff --git a/client/app/routes/products/list.tsx b/client/app/routes/products/list.tsx index 3120e4e..73944e6 100644 --- a/client/app/routes/products/list.tsx +++ b/client/app/routes/products/list.tsx @@ -3,16 +3,12 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import _ from "lodash"; import { - Archive, Check, - Download, HistoryIcon, Plus, - Share, - Star, + RefreshCcw, Trash2, UploadCloud, - UserPlus, X, } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; @@ -25,6 +21,7 @@ import { type TableState, } from "~/components/core/data-table"; import Loader from "~/components/loader"; +import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; import { Card, @@ -33,18 +30,16 @@ import { CardHeader, CardTitle, } from "~/components/ui/card"; +import { Textarea } from "~/components/ui/textarea"; import { delay } from "~/features/delay"; import { stateToURLQuery, urlQueryToState, } from "~/features/state-url-converter"; import { useAppSelector } from "~/hooks/use-app-dispatch"; -import { useUsersContext } from "~/layouts/contexts/user-layout.context"; import type { RootState } from "~/store"; -import ProductModal from "./components/modals/product-modal"; -import { Badge } from "~/components/ui/badge"; import { HistoryModal } from "./components/modals/history-modal"; -import { Textarea } from "~/components/ui/textarea"; +import ProductModal from "./components/modals/product-modal"; export default function List() { // const { setEdit } = useUserModal(); @@ -52,6 +47,8 @@ export default function List() { const [openHistories, setOpenHistories] = useState([]); + const [openEdit, setOpenEdit] = useState(null); + const prevStates = useRef>(null); const [initStates, setInitStates] = useState | undefined>( @@ -217,6 +214,15 @@ export default function List() { handlePublist(data); }, }, + { + key: "re-publist", + label: "Re publist", + icon: , + action: (data: IProduct) => { + handleRePublist(data); + }, + show: (data: IProduct) => data.status, + }, { key: "histories", label: "Histories", @@ -277,6 +283,18 @@ export default function List() { }, }); + const rePublistMutation = useMutation({ + mutationFn: async (data: Partial) => { + await delay(300); + + return productApi.customAction(data.id || 0, "re-publist", data); + }, + onSuccess: (data) => { + console.log({ data }); + refetch(); + }, + }); + const handlePublist = async (data: Partial) => { toast.promise( listingMutation.mutateAsync(data), // gọi function để trả về promise @@ -288,6 +306,17 @@ export default function List() { ); }; + const handleRePublist = async (data: Partial) => { + toast.promise( + rePublistMutation.mutateAsync(data), // gọi function để trả về promise + { + loading: "Loading...", + success: (result) => `${data?.title} toast has been added`, + error: "Error", + } + ); + }; + if (!initStates) return ; return ( @@ -359,7 +388,13 @@ export default function List() { ); }} onDelete={handleDelete} - // onEdit={setEdit} + onEdit={(data: IProduct, children) => { + return ( + + {children} + + ); + }} options={{ disableDel(data) { return data.id === user?.id; diff --git a/server/public/medias/products/edited-product-asr-9901-chassis-payg-120g-base-hw-piddd/product-53-1755224843970.jpg b/server/public/medias/products/edited-product-asr-9901-chassis-payg-120g-base-hw-piddd/product-53-1755224843970.jpg new file mode 100644 index 0000000..8fe79d1 Binary files /dev/null and b/server/public/medias/products/edited-product-asr-9901-chassis-payg-120g-base-hw-piddd/product-53-1755224843970.jpg differ diff --git a/server/public/medias/products/meraki-ms120-48fp-1g-l2-cld-managed-48x-gige-740w-poe-switch/product-60-1755313243202.jpg b/server/public/medias/products/meraki-ms120-48fp-1g-l2-cld-managed-48x-gige-740w-poe-switch/product-60-1755313243202.jpg new file mode 100644 index 0000000..eb4f22d Binary files /dev/null and b/server/public/medias/products/meraki-ms120-48fp-1g-l2-cld-managed-48x-gige-740w-poe-switch/product-60-1755313243202.jpg differ diff --git a/server/public/medias/products/meraki-ms120-48fp-1g-l2-cld-managed-48x-gige-740w-poe-switch/product-60-1755313243203.jpg b/server/public/medias/products/meraki-ms120-48fp-1g-l2-cld-managed-48x-gige-740w-poe-switch/product-60-1755313243203.jpg new file mode 100644 index 0000000..6d880ee Binary files /dev/null and b/server/public/medias/products/meraki-ms120-48fp-1g-l2-cld-managed-48x-gige-740w-poe-switch/product-60-1755313243203.jpg differ diff --git a/server/public/medias/products/wegewgewgew/product-1755154798642.png b/server/public/medias/products/wegewgewgew/product-41-1755154798642.png similarity index 100% rename from server/public/medias/products/wegewgewgew/product-1755154798642.png rename to server/public/medias/products/wegewgewgew/product-41-1755154798642.png diff --git a/server/public/medias/products/wegwegwegew/product-58-1755312884011.png b/server/public/medias/products/wegwegwegew/product-58-1755312884011.png new file mode 100644 index 0000000..230a55b Binary files /dev/null and b/server/public/medias/products/wegwegwegew/product-58-1755312884011.png differ diff --git a/server/public/medias/products/wegwegwewe/product-1755067588211.png b/server/public/medias/products/wegwegwewe/product-1755067588211.png deleted file mode 100644 index b338f16..0000000 Binary files a/server/public/medias/products/wegwegwewe/product-1755067588211.png and /dev/null differ diff --git a/server/public/medias/products/wegwgwegwegew/product-67-1755314045883.jpg b/server/public/medias/products/wegwgwegwegew/product-67-1755314045883.jpg new file mode 100644 index 0000000..eb4f22d Binary files /dev/null and b/server/public/medias/products/wegwgwegwegew/product-67-1755314045883.jpg differ diff --git a/server/public/medias/products/wegwgwegwegew/product-67-1755314724835.jpg b/server/public/medias/products/wegwgwegwegew/product-67-1755314724835.jpg new file mode 100644 index 0000000..8a7851e Binary files /dev/null and b/server/public/medias/products/wegwgwegwegew/product-67-1755314724835.jpg differ diff --git a/server/public/medias/products/wgwgwegwe/product-1755047952582.png b/server/public/medias/products/wgwgwegwe/product-1755047952582.png deleted file mode 100644 index 2735d7a..0000000 Binary files a/server/public/medias/products/wgwgwegwe/product-1755047952582.png and /dev/null differ diff --git a/server/src/entities/product.entity.ts b/server/src/entities/product.entity.ts index 62d8d66..768a1b1 100644 --- a/server/src/entities/product.entity.ts +++ b/server/src/entities/product.entity.ts @@ -55,6 +55,9 @@ export class Product extends CoreEntity { @Column({ type: 'varchar', unique: true }) sku: string; + @Column({ type: 'varchar', unique: true, nullable: true, default: null }) + publist_id: string; + @Column({ type: 'varchar', nullable: true }) location?: string; diff --git a/server/src/modules/medias/medias.controller.ts b/server/src/modules/medias/medias.controller.ts index 78d4845..b9a42c7 100644 --- a/server/src/modules/medias/medias.controller.ts +++ b/server/src/modules/medias/medias.controller.ts @@ -1,55 +1,26 @@ -import { Controller, Get, NotFoundException, Param, Res } from '@nestjs/common'; -import * as path from 'path'; -import * as fs from 'fs'; +import { Controller, Delete, Get, Param, Res } from '@nestjs/common'; import { Response } from 'express'; -import { SystemLang } from '@/system/lang/system.lang'; +import { MediasService } from './medias.service'; @Controller('medias') export class MediasController { - @Get('avatars/:username/:filename') - async avartarImage( - @Param('username') username: string, + constructor(private readonly mediaService: MediasService) {} + + @Get(':type/:subdir/:filename') + async serveMedia( + @Param('type') type: 'avatars' | 'products', + @Param('subdir') subdir: string, @Param('filename') filename: string, @Res() res: Response, ) { - const filePath = path.join( - process.cwd(), - 'public', - 'medias', - 'avatars', - username, - filename, - ); - - if (!fs.existsSync(filePath)) { - throw new NotFoundException( - SystemLang.getText('messages', 'file_not_found'), - ); - } - - res.sendFile(filePath); + return this.mediaService.serveMedia(type, subdir, filename, res); } - @Get('products/:title/:filename') - async serveImage( - @Param('title') title: string, + @Delete(':type/:subdir/:filename') + async deleteMedia( + @Param('type') type: 'avatars' | 'products', + @Param('subdir') subdir: string, @Param('filename') filename: string, - @Res() res: Response, ) { - const filePath = path.join( - process.cwd(), - 'public', - 'medias', - 'products', - title, - filename, - ); - - if (!fs.existsSync(filePath)) { - throw new NotFoundException( - SystemLang.getText('messages', 'file_not_found'), - ); - } - - res.sendFile(filePath); + return this.mediaService.deleteMedia(type, subdir, filename); } } diff --git a/server/src/modules/medias/medias.service.ts b/server/src/modules/medias/medias.service.ts index 382a902..8cf3e1b 100644 --- a/server/src/modules/medias/medias.service.ts +++ b/server/src/modules/medias/medias.service.ts @@ -1,13 +1,25 @@ -import { Injectable, BadRequestException } from '@nestjs/common'; +import { + Injectable, + BadRequestException, + NotFoundException, + ForbiddenException, +} from '@nestjs/common'; import { writeFile } from 'fs/promises'; import * as fs from 'fs'; import * as path from 'path'; import sizeOf from 'image-size'; import { promises } from 'fs'; import axios from 'axios'; +import { SystemLang } from '@/system/lang/system.lang'; +import { Response } from 'express'; +import AppResponse from '@/system/response/ktq-response'; +import { EventEmitter2 } from '@nestjs/event-emitter'; @Injectable() export class MediasService { + constructor(private eventEmitter: EventEmitter2) {} + private ROOT_MEDIA_FOLDER = './public'; + private readonly allowedTypes = ['avatars', 'products']; async saveBase64Image( base64: string, @@ -133,6 +145,19 @@ export class MediasService { }; } + /** + * Extract ID from filename + * Example: "product-51-1755224843970.jpg" => 51 + * Example: "avatar-123-1755224843970.png" => 123 + */ + extractIdFromFilename(filename: string): number | null { + const match = filename.match(/^[^-]+-(\d+)-/); + if (match) { + return Number(match[1]); + } + return null; + } + async downloadImageAndSave( url: string, folder: string, @@ -193,6 +218,44 @@ export class MediasService { } } + async renameMediasFolder( + oldFilepath: string, + folder: string, + newFilename: string, + ): Promise { + if (!oldFilepath) return; + + // Đường dẫn cũ + const oldRelativePath = path.join('medias/' + folder, oldFilepath); + const oldFullPath = path.join(this.ROOT_MEDIA_FOLDER, oldRelativePath); + + // Đường dẫn mới + const newRelativePath = path.join('medias/' + folder, newFilename); + const newFullPath = path.join(this.ROOT_MEDIA_FOLDER, newRelativePath); + + const rootPath = path.resolve(this.ROOT_MEDIA_FOLDER); + const resolvedOldFullPath = path.resolve(oldFullPath); + const resolvedNewFullPath = path.resolve(newFullPath); + + // Ngăn đổi tên ra ngoài thư mục ROOT_MEDIA_FOLDER + if ( + !resolvedOldFullPath.startsWith(rootPath) || + !resolvedNewFullPath.startsWith(rootPath) + ) { + throw new Error('Invalid: Cannot rename outside the specified directory'); + } + + try { + if (fs.existsSync(resolvedOldFullPath)) { + await fs.promises.rename(resolvedOldFullPath, resolvedNewFullPath); + } else { + console.warn(`File not found: ${oldFullPath}`); + } + } catch (error) { + console.error(`Error when renaming file: ${error.message}`); + } + } + private _getExtensionFromMime(mime: string): string | null { const map = { 'image/jpeg': 'jpg', @@ -210,4 +273,84 @@ export class MediasService { const dd = String(now.getDate()).padStart(2, '0'); return [yyyy, mm, dd]; } + + private buildFilePath( + type: string, + subdir: string, + filename: string, + ): string { + return path.join(process.cwd(), 'public', 'medias', type, subdir, filename); + } + + private validateType(type: string) { + if (!this.allowedTypes.includes(type)) { + throw new NotFoundException( + SystemLang.getText('messages', 'file_not_found'), + ); + } + } + + async serveMedia( + type: 'avatars' | 'products', + subdir: string, + filename: string, + res: Response, + ) { + this.validateType(type); + + const filePath = this.buildFilePath(type, subdir, filename); + + if (!fs.existsSync(filePath)) { + throw new NotFoundException( + SystemLang.getText('messages', 'file_not_found'), + ); + } + + return res.sendFile(filePath); + } + + async deleteMedia( + type: 'avatars' | 'products', + subdir: string, + filename: string, + ) { + if (!['avatars', 'products'].includes(type)) { + throw new ForbiddenException( + SystemLang.getText('messages', 'file_not_found'), + ); + } + + const filePath = path.join( + process.cwd(), + 'public', + 'medias', + type, + subdir, + filename, + ); + + if (!fs.existsSync(filePath)) { + throw new NotFoundException( + SystemLang.getText('messages', 'file_not_found'), + ); + } + + try { + fs.unlinkSync(filePath); + + // bắn event sau khi xóa + this.eventEmitter.emit('media.deleted', { + type, + subdir, + filename, + filePath, + }); + + return AppResponse.toResponse(true); + } catch (err) { + throw new ForbiddenException( + SystemLang.getText('messages', 'file_delete_failed'), + ); + } + } } diff --git a/server/src/modules/products/dtos/create-product.dto.ts b/server/src/modules/products/dtos/create-product.dto.ts index 2b1bece..1bdb233 100644 --- a/server/src/modules/products/dtos/create-product.dto.ts +++ b/server/src/modules/products/dtos/create-product.dto.ts @@ -60,4 +60,8 @@ export class CreateProductDto { @IsOptional() @IsString() location?: string; + + @IsOptional() + @IsString() + publist_id?: string; } diff --git a/server/src/modules/products/dtos/publist-finish.dto.ts b/server/src/modules/products/dtos/publist-finish.dto.ts index aaeec6c..08ed339 100644 --- a/server/src/modules/products/dtos/publist-finish.dto.ts +++ b/server/src/modules/products/dtos/publist-finish.dto.ts @@ -1,10 +1,18 @@ import { IsBoolean, IsOptional, IsString } from 'class-validator'; -export class PublistFinishDto { +export class HistoryDto { @IsBoolean() published: boolean; @IsString() @IsOptional() error?: string; + + @IsString() + @IsOptional() + message?: string; + + @IsString() + @IsOptional() + publist_id?: string; } diff --git a/server/src/modules/products/dtos/update-product.dto.ts b/server/src/modules/products/dtos/update-product.dto.ts new file mode 100644 index 0000000..cb72ca4 --- /dev/null +++ b/server/src/modules/products/dtos/update-product.dto.ts @@ -0,0 +1,61 @@ +import { Product } from '@/entities/product.entity'; +import { SystemLang } from '@/system/lang/system.lang'; +import { IsExistInDatabase } from '@/system/validators/decorators/is-exist-in-database'; +import { Type } from 'class-transformer'; +import { IsArray, IsNumber, IsOptional, IsString, Min } from 'class-validator'; + +export class UpdateProductDto { + @IsNumber() + id: number; + + @IsString() + @IsOptional() + title: string; + + @IsArray() + @IsString({ each: true }) + @IsOptional() + images: string[]; + + @Type(() => Number) // đảm bảo chuyển đổi từ string -> number khi gửi form-data + @IsNumber({ maxDecimalPlaces: 2 }) + @Min(0) + @IsOptional() + price: number; + + @IsString() + @IsOptional() + category: string; + + @IsString() + @IsOptional() + condition: string; + + @IsString() + @IsOptional() + description: string; + + @IsArray() + @IsString({ each: true }) + @IsOptional() + tags: string[]; + + @IsString() + @IsOptional() + @IsExistInDatabase( + Product, + 'sku', + {}, + { + message: SystemLang.getCustomText({ + en: 'SKU must be unique', + vi: 'SKU phải là duy nhất', + }), + }, + ) + sku: string; + + @IsOptional() + @IsString() + location?: string; +} diff --git a/server/src/modules/products/products-listener.event.ts b/server/src/modules/products/products-listener.event.ts new file mode 100644 index 0000000..1110c5f --- /dev/null +++ b/server/src/modules/products/products-listener.event.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { ProductsService } from './products.service'; +import { MediasService } from '../medias/medias.service'; + +@Injectable() +export class ProductsListener { + constructor( + private readonly service: ProductsService, + private readonly mediaService: MediasService, + ) {} + + @OnEvent('media.deleted') + async handleMediaDeletedEvent(payload: { + type: string; + subdir: string; + filename: string; + filePath: string; + }) { + const id = this.mediaService.extractIdFromFilename(payload.filename); + + const product = await this.service.repo.findOne({ where: { id } }); + if (!product) return; + + // Loại bỏ ảnh trùng với filename + product.images = product.images.filter( + (image) => !image.includes(payload.filename), + ); + + await this.service.repo.update(product.id, product); + } +} diff --git a/server/src/modules/products/products.controller.ts b/server/src/modules/products/products.controller.ts index 2097d1e..507d9e6 100644 --- a/server/src/modules/products/products.controller.ts +++ b/server/src/modules/products/products.controller.ts @@ -1,10 +1,12 @@ import { Product } from '@/entities/product.entity'; import CoreController from '@/system/core/core-controller'; -import { Body, Controller, Param, Post, Sse } from '@nestjs/common'; +import { Body, Controller, Param, Post, Put, Req, Sse } from '@nestjs/common'; import { ProductsService } from './products.service'; import { CreateProductDto } from './dtos/create-product.dto'; -import { PublistFinishDto } from './dtos/publist-finish.dto'; +import { HistoryDto } from './dtos/publist-finish.dto'; import { SyncDto } from './dtos/syncs.dto'; +import { UpdateProductDto } from './dtos/update-product.dto'; +import { Request } from 'express'; @Controller('products') export class ProductsController extends CoreController< @@ -19,6 +21,14 @@ export class ProductsController extends CoreController< async create(@Body() data: CreateProductDto): Promise { return this.service.create(data); } + @Put(':id') + async update( + @Body() data: UpdateProductDto, + @Param('id') id: Product['id'], + @Req() request: Request, + ): Promise { + return this.service.update(id, data as any, request); + } @Post('sync') async sync(@Body() data: SyncDto): Promise { @@ -35,21 +45,35 @@ export class ProductsController extends CoreController< return this.service.unlist(id); } + @Post('re-publist/:id') + async rePublist(@Param('id') id: Product['id']): Promise { + return this.service.rePublist(id); + } + @Post('publist-finish/:id') async publistFinish( @Param('id') id: Product['id'], - @Body() data: PublistFinishDto, + @Body() data: HistoryDto, ): Promise { return this.service.publistFinish(id, data); } + @Post('delete-finish/:id') async deleteFinish( @Param('id') id: Product['id'], - @Body() data: PublistFinishDto, + @Body() data: HistoryDto, ): Promise { return this.service.deleteFinish(id, data); } + @Post('update-finish/:id') + async updateFinish( + @Param('id') id: Product['id'], + @Body() data: HistoryDto, + ): Promise { + return this.service.updateFinish(id, data); + } + @Sse('publist-stream') async publistStream() { return this.service.sendPublistEvents(); @@ -59,4 +83,9 @@ export class ProductsController extends CoreController< async deleteStream() { return this.service.sendDeleteEvents(); } + + @Sse('edit-stream') + async editStream() { + return this.service.sendEditEvents(); + } } diff --git a/server/src/modules/products/products.module.ts b/server/src/modules/products/products.module.ts index d4fd204..5790178 100644 --- a/server/src/modules/products/products.module.ts +++ b/server/src/modules/products/products.module.ts @@ -6,6 +6,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { MediasModule } from '../medias/medias.module'; import { EventsModule } from '../events/events.module'; import { PublistHistory } from '@/entities/publist-history.entity'; +import { ProductsListener } from './products-listener.event'; @Module({ imports: [ @@ -13,7 +14,7 @@ import { PublistHistory } from '@/entities/publist-history.entity'; MediasModule, EventsModule, ], - providers: [ProductsService], + providers: [ProductsService, ProductsListener], controllers: [ProductsController], }) export class ProductsModule {} diff --git a/server/src/modules/products/products.service.ts b/server/src/modules/products/products.service.ts index 6595078..830b0cb 100644 --- a/server/src/modules/products/products.service.ts +++ b/server/src/modules/products/products.service.ts @@ -17,19 +17,24 @@ import { Repository } from 'typeorm'; import { EventsService } from '../events/events.service'; import { MediasService } from '../medias/medias.service'; import { CreateProductDto } from './dtos/create-product.dto'; -import { PublistFinishDto } from './dtos/publist-finish.dto'; +import { HistoryDto } from './dtos/publist-finish.dto'; import { Request } from 'express'; import { SyncDto } from './dtos/syncs.dto'; +import { Paginated } from 'nestjs-paginate'; @Injectable() export class ProductsService extends CoreService { public static EVENTS = { SEND_PUBLIST: 'send-publist', SEND_DELETE: 'send-delete', + SEND_EDIT: 'send-edit', PUBLIST_FINISH: 'publist-finish', DElETE_FINISH: 'delete-finish', + EDIT_FINISH: 'edit-finish', }; + private TIMEOUT_WATING = 60000; + constructor( @InjectRepository(Product) readonly repo: Repository, @@ -50,14 +55,22 @@ export class ProductsService extends CoreService { sku: true, created_at: true, }, - relations: { - histories: true, - }, }, Product, ); } + protected async beforeIndex(data: Paginated): Promise { + for (const item of data.data) { + const histories = await this.historiesRepo.find({ + where: { product: { id: item.id } }, + order: { created_at: 'DESC' }, + }); + + item.histories = histories; + } + } + protected async afterDelete( id: number, data: Partial, @@ -74,40 +87,15 @@ export class ProductsService extends CoreService { } } - // protected async beforeDelete( - // id: number, - // data: Partial, - // req: Request, - // ): Promise { - // // Emit sự kiện để bắt đầu publish - // this.eventService.sendEvent(ProductsService.EVENTS.SEND_DELETE, { - // ...data, - // }); - - // // Đợi phản hồi từ client - // const result = await this.waitForDeleteResult({ ...data, id }); - - // if (!result) - // throw new BadRequestException( - // AppResponse.toResponse(false, { - // message: SystemLang.getText('messages', 'try_again'), - // }), - // ); - - // return true; - // } - - async create(data: CreateProductDto): Promise { - const product = this.repo.create(data); - - if (data.images && data.images.length) { + async mapImages(data: Product, images: string[]) { + if (images && images.length) { const newImages = []; - for (const image of data.images) { + for (const image of images) { if (image.startsWith('data:')) { const result = await this.mediasService.saveBase64Image( image, - 'product', + `product-${data.id}`, `medias/products/${data.title.toLowerCase().replaceAll(' ', '-')}`, ); newImages.push(result.filename); @@ -116,7 +104,7 @@ export class ProductsService extends CoreService { ) { const result = await this.mediasService.downloadImageAndSave( image, - 'product', + `product-${data.id}`, `medias/products/${data.title.toLowerCase().replaceAll(' ', '-')}`, ); newImages.push(result.filename); @@ -126,15 +114,127 @@ export class ProductsService extends CoreService { } if (newImages.length) { - product.images = newImages; + return newImages; } } - await this.repo.save(product); + return []; + } + + async create(data: CreateProductDto): Promise { + const product = this.repo.create(data); + + const result = await this.repo.save({ ...product, images: [] }); + + const newImages = await this.mapImages(result, data.images); + + result.images = newImages; + + await this.repo.update(result.id, result); + + return AppResponse.toResponse(result); + } + + async update( + id: number, + { images, ...data }: Partial | any, + request: Request, + ): Promise { + const prev = await this.repo.findOne({ where: { id } }); + + if (!prev) + throw new NotFoundException( + AppResponse.toResponse(null, { + message: SystemLang.getText('messages', 'not_found'), + }), + ); + + const newImages = await this.mapImages(prev, images); + + data.images = [...prev.images, ...newImages]; + + const result = await this.repo.update(id, data); + + if (!result) throw new BadRequestException(AppResponse.toResponse(false)); + + if (data?.title) { + await this.mediasService.renameMediasFolder( + `${prev.title.toLowerCase().replaceAll(' ', '-')}` as string, + 'products', + `${data.title.toLowerCase().replaceAll(' ', '-')}` as string, + ); + } + + const product = await this.repo.findOne({ where: { id } }); + + // const plainData = plainToClass(Product, product); + + // let base64Image: string[] = []; + + // try { + // base64Image = await this.toImagesBase64(plainData); + // } catch (error) { + // console.error('Error converting images to Base64:', error); + // throw new BadRequestException( + // AppResponse.toResponse(null, { + // message: SystemLang.getText('messages', 'server_error'), + // }), + // ); + // } + + // this.eventService.sendEvent(ProductsService.EVENTS.SEND_EDIT, { + // prev: prev, + // data: { ...plainData, images: base64Image }, + // }); + + // const { publist_id } = await this.waitForEditResult(prev, product); + + // if (publist_id && !product.publist_id) { + // await this.repo.update(id, { publist_id }); + // } return AppResponse.toResponse(product); } + async rePublist(id: Product['id']) { + const product = await this.repo.findOne({ where: { id } }); + + if (!product) { + throw new NotFoundException( + AppResponse.toResponse(null, { + message: SystemLang.getText('messages', 'not_found'), + }), + ); + } + + const plainData = plainToClass(Product, product); + let base64Image: string[] = []; + + try { + base64Image = await this.toImagesBase64(plainData); + } catch (error) { + console.error('Error converting images to Base64:', error); + throw new BadRequestException( + AppResponse.toResponse(null, { + message: SystemLang.getText('messages', 'server_error'), + }), + ); + } + + this.eventService.sendEvent(ProductsService.EVENTS.SEND_EDIT, { + prev: product, + data: { ...plainData, images: base64Image }, + }); + + const { publist_id } = await this.waitForEditResult(product, product); + + if (publist_id && !product.publist_id) { + await this.repo.update(id, { publist_id }); + } + + return AppResponse.toResponse(plainData); + } + async toImagesBase64(product: Product) { const images = []; @@ -230,7 +330,7 @@ export class ProductsService extends CoreService { try { return await this.eventService.waitForEvent( `${ProductsService.EVENTS.PUBLIST_FINISH}_${data.id}`, - 60000, + this.TIMEOUT_WATING, ); } catch { throw new BadRequestException( @@ -247,7 +347,7 @@ export class ProductsService extends CoreService { try { return await this.eventService.waitForEvent( `${ProductsService.EVENTS.DElETE_FINISH}_${data.id}`, - 60000, + this.TIMEOUT_WATING, ); } catch { throw new BadRequestException( @@ -258,7 +358,22 @@ export class ProductsService extends CoreService { } } - async publistFinish(id: Product['id'], data: PublistFinishDto) { + private async waitForEditResult(prev: Product, data: Product): Promise { + try { + return await this.eventService.waitForEvent( + `${ProductsService.EVENTS.EDIT_FINISH}_${prev.id}`, + this.TIMEOUT_WATING, + ); + } catch { + throw new BadRequestException( + AppResponse.toResponse(null, { + message: SystemLang.getText('messages', 'try_again'), + }), + ); + } + } + + async publistFinish(id: Product['id'], data: HistoryDto) { const product = await this.repo.findOne({ where: { id } }); if (!product) @@ -280,7 +395,7 @@ export class ProductsService extends CoreService { return AppResponse.toResponse(plainData); } - async deleteFinish(id: Product['id'], data: PublistFinishDto) { + async deleteFinish(id: Product['id'], data: HistoryDto) { const product = await this.repo.findOne({ where: { id } }); if (!product) @@ -302,8 +417,32 @@ export class ProductsService extends CoreService { return AppResponse.toResponse(plainData); } + async updateFinish(id: Product['id'], { publist_id, ...data }: HistoryDto) { + const product = await this.repo.findOne({ where: { id } }); + + if (!product) + throw new NotFoundException( + AppResponse.toResponse(null, { + message: SystemLang.getText('messages', 'not_found'), + }), + ); + + const plainData = plainToClass(Product, product); + + const result = await this.historiesRepo.save({ + ...data, + product: { id: product.id }, + }); + + this.eventService.sendEvent( + `${ProductsService.EVENTS.EDIT_FINISH}_${plainData.id}`, + { history: plainToClass(PublistHistory, result), publist_id }, + ); + + return AppResponse.toResponse(plainData); + } + sendPublistEvents(): Observable<{ data: any }> { - // Tạo Observable dựa trên sự kiện 'send publist' của eventEmitter return fromEvent( this.eventService.event, ProductsService.EVENTS.SEND_PUBLIST, @@ -315,7 +454,6 @@ export class ProductsService extends CoreService { } sendDeleteEvents(): Observable<{ data: any }> { - // Tạo Observable dựa trên sự kiện 'send publist' của eventEmitter return fromEvent( this.eventService.event, ProductsService.EVENTS.SEND_DELETE, @@ -326,7 +464,46 @@ export class ProductsService extends CoreService { ); } + sendEditEvents(): Observable<{ data: any }> { + return fromEvent( + this.eventService.event, + ProductsService.EVENTS.SEND_EDIT, + ).pipe( + map((data) => ({ + data, // gửi data về client + })), + ); + } + async sync({ items }: SyncDto) { + // Nếu items rỗng thì disable toàn bộ sản phẩm + if (!items.length) { + const products = await this.repo.find({ select: ['id'] }); + + if (products.length) { + await this.repo + .createQueryBuilder() + .update(Product) + .set({ status: false }) + .whereInIds(products.map((p) => p.id)) + .execute(); + + await this.historiesRepo.save( + products.map((product) => ({ + message: `Product was disabled (empty sync) at: ${new Date().toISOString()}`, + product, + published: false, + })), + ); + } + + return { + disabled: products.length, + enabled: 0, + inserted: 0, + }; + } + const incomingTitles = new Set(items.map((d) => d.title)); // Lấy danh sách sản phẩm hiện tại trong DB @@ -380,18 +557,6 @@ export class ProductsService extends CoreService { await this.repo.update(product.id, { status: true, price }); } - // Insert sản phẩm mới + log - if (toInsert.length) { - const newProducts = this.repo.create( - toInsert.map((item) => ({ - title: item.title, - price: item.price, - status: true, - })), - ); - await this.repo.save(newProducts); - } - return { disabled: toDisable.length, enabled: toEnable.length, diff --git a/server/src/system/lang/system.lang.ts b/server/src/system/lang/system.lang.ts index ab6c1da..61ee6ff 100644 --- a/server/src/system/lang/system.lang.ts +++ b/server/src/system/lang/system.lang.ts @@ -37,6 +37,7 @@ export class SystemLang { 'Đổi mật khẩu thành công. Vui lòng đăng nhập lại !', too_many_request: 'Yêu cầu vượt quá mức quy định', feature_disabled: 'Tính năng này đang tạm ngưng', + file_delete_failed: 'Không thể xóa tệp. Vui lòng thử lại sau.', }, en: { error: 'An error occurred!', @@ -74,6 +75,8 @@ export class SystemLang { change_pass_success: 'Change password success. Please re-login !', too_many_request: 'To many request', feature_disabled: 'This feature is disabled', + file_delete_failed: + 'Failed to delete the file. Please try again later.', }, }, };