lotusbail
Version:
WhatsApp API x Button by PL
37 lines (36 loc) • 15.4 kB
JavaScript
var __createBinding=this&&this.__createBinding||(Object.create?function(a,b,c,e){void 0===e&&(e=c);var d=Object.getOwnPropertyDescriptor(b,c);if(!d||("get"in d?!b.__esModule:d.writable||d.configurable))d={enumerable:!0,get:function(){return b[c]}};Object.defineProperty(a,e,d)}:function(a,b,c,e){void 0===e&&(e=c);a[e]=b[c]}),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?function(a,b){Object.defineProperty(a,"default",{enumerable:!0,value:b})}:function(a,b){a["default"]=b}),__importStar=
this&&this.__importStar||function(){var a=function(b){a=Object.getOwnPropertyNames||function(c){var e=[],d;for(d in c)Object.prototype.hasOwnProperty.call(c,d)&&(e[e.length]=d);return e};return a(b)};return function(b){if(b&&b.__esModule)return b;var c={};if(null!=b)for(var e=a(b),d=0;d<e.length;d++)"default"!==e[d]&&__createBinding(c,b,e[d]);__setModuleDefault(c,b);return c}}(),__importDefault=this&&this.__importDefault||function(a){return a&&a.__esModule?a:{"default":a}};
Object.defineProperty(exports,"__esModule",{value:!0});
exports.getStatusCodeForMediaRetry=exports.decryptMediaRetryData=exports.decodeMediaRetryNode=exports.encryptMediaRetryRequest=exports.getWAUploadToServer=exports.downloadEncryptedContent=exports.downloadContentFromMessage=exports.getUrlFromDirectPath=exports.encryptedStream=exports.getHttpStream=exports.getStream=exports.toBuffer=exports.toReadable=exports.mediaMessageSHA256B64=exports.generateProfilePicture=exports.encodeBase64EncodedStringForUpload=exports.extractImageThumb=exports.getRawMediaUploadData=
exports.hkdfInfoKey=void 0;exports.getMediaKeys=getMediaKeys;exports.getAudioDuration=getAudioDuration;exports.getAudioWaveform=getAudioWaveform;exports.generateThumbnail=generateThumbnail;exports.extensionForMediaMessage=extensionForMediaMessage;
const boom_1=require("@hapi/boom"),axios_1=__importDefault(require("axios")),child_process_1=require("child_process"),Crypto=__importStar(require("crypto")),events_1=require("events"),fs_1=require("fs"),os_1=require("os"),path_1=require("path"),stream_1=require("stream"),WAProto_1=require("../../WAProto"),Defaults_1=require("../Defaults"),WABinary_1=require("../WABinary"),crypto_1=require("./crypto"),generics_1=require("./generics"),getTmpFilesDirectory=()=>(0,os_1.tmpdir)(),getImageProcessingLibrary=
async()=>{const [a,b]=await Promise.all([Promise.resolve().then(()=>__importStar(require("jimp"))).catch(()=>{}),Promise.resolve().then(()=>__importStar(require("sharp"))).catch(()=>{})]);if(b)return{sharp:b};if(a)return{jimp:a};throw new boom_1.Boom("No image processing library available");},hkdfInfoKey=a=>`WhatsApp ${Defaults_1.MEDIA_HKDF_KEY_MAPPING[a]} Keys`;exports.hkdfInfoKey=hkdfInfoKey;
const getRawMediaUploadData=async(a,b,c)=>{({stream:a}=await (0,exports.getStream)(a));null===c||void 0===c||c.debug("got stream for raw upload");const e=Crypto.createHash("sha256");b=(0,path_1.join)((0,os_1.tmpdir)(),b+(0,generics_1.generateMessageIDV2)());const d=(0,fs_1.createWriteStream)(b);let h=0;try{for await(const l of a)h+=l.length,e.update(l),d.write(l)||await (0,events_1.once)(d,"drain");d.end();await (0,events_1.once)(d,"finish");a.destroy();const k=e.digest();null===c||void 0===c||c.debug("hashed data for raw upload");
return{filePath:b,fileSha256:k,fileLength:h}}catch(k){d.destroy();a.destroy();try{await fs_1.promises.unlink(b)}catch(l){}throw k;}};exports.getRawMediaUploadData=getRawMediaUploadData;async function getMediaKeys(a,b){if(!a)throw new boom_1.Boom("Cannot derive from empty media key");"string"===typeof a&&(a=Buffer.from(a.replace("data:;base64,",""),"base64"));a=await (0,crypto_1.hkdf)(a,112,{info:(0,exports.hkdfInfoKey)(b)});return{iv:a.slice(0,16),cipherKey:a.slice(16,48),macKey:a.slice(48,80)}}
const extractVideoThumb=async(a,b,c,e)=>new Promise((d,h)=>{(0,child_process_1.exec)(`ffmpeg -ss ${c} -i ${a} -y -vf scale=${e.width}:-1 -vframes 1 -f image2 ${b}`,k=>{k?h(k):d()})}),extractImageThumb=async(a,b=32)=>{var c,e;a instanceof stream_1.Readable&&(a=await (0,exports.toBuffer)(a));var d=await getImageProcessingLibrary();if("sharp"in d&&"function"===typeof(null===(c=d.sharp)||void 0===c?void 0:c.default))return d=d.sharp.default(a),a=await d.metadata(),{buffer:await d.resize(b).jpeg({quality:50}).toBuffer(),
original:{width:a.width,height:a.height}};if("jimp"in d&&"object"===typeof(null===(e=d.jimp)||void 0===e?void 0:e.Jimp))return a=await d.jimp.default.Jimp.read(a),c={width:a.width,height:a.height},{buffer:await a.resize({w:b,mode:d.jimp.ResizeStrategy.BILINEAR}).getBuffer("image/jpeg",{quality:50}),original:c};throw new boom_1.Boom("No image processing library available");};exports.extractImageThumb=extractImageThumb;
const encodeBase64EncodedStringForUpload=a=>encodeURIComponent(a.replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""));exports.encodeBase64EncodedStringForUpload=encodeBase64EncodedStringForUpload;
const generateProfilePicture=async(a,b)=>{var c,e;const {width:d=640,height:h=640}=b||{};Buffer.isBuffer(a)?b=a:({stream:a}=await (0,exports.getStream)(a),b=await (0,exports.toBuffer)(a));a=await getImageProcessingLibrary();if("sharp"in a&&"function"===typeof(null===(c=a.sharp)||void 0===c?void 0:c.default))c=a.sharp.default(b).resize(d,h).jpeg({quality:50}).toBuffer();else if("jimp"in a&&"object"===typeof(null===(e=a.jimp)||void 0===e?void 0:e.Jimp))c=await a.jimp.default.Jimp.read(b),e=Math.min(c.width,
c.height),c=c.crop({x:0,y:0,w:e,h:e}).resize({w:d,h,mode:a.jimp.ResizeStrategy.BILINEAR}).getBuffer("image/jpeg",{quality:50});else throw new boom_1.Boom("No image processing library available");return{img:await c}};exports.generateProfilePicture=generateProfilePicture;const mediaMessageSHA256B64=a=>{a=Object.values(a)[0];return(null===a||void 0===a?void 0:a.fileSha256)&&Buffer.from(a.fileSha256).toString("base64")};exports.mediaMessageSHA256B64=mediaMessageSHA256B64;
async function getAudioDuration(a){const b=await Promise.resolve().then(()=>__importStar(require("music-metadata"))),c={duration:!0};return(Buffer.isBuffer(a)?await b.parseBuffer(a,void 0,c):"string"===typeof a?await b.parseFile(a,c):await b.parseStream(a,void 0,c)).format.duration}
async function getAudioWaveform(a,b){try{var {default:c}=await eval("import('audio-decode')");if(Buffer.isBuffer(a))var e=a;else if("string"===typeof a){var d=(0,fs_1.createReadStream)(a);e=await (0,exports.toBuffer)(d)}else e=await (0,exports.toBuffer)(a);const h=(await c(e)).getChannelData(0),k=Math.floor(h.length/64);a=[];for(c=0;64>c;c++){e=k*c;d=0;for(let m=0;m<k;m++)d+=Math.abs(h[e+m]);a.push(d/k)}const l=Math.pow(Math.max(...a),-1),r=a.map(m=>m*l);return new Uint8Array(r.map(m=>Math.floor(100*
m)))}catch(h){null===b||void 0===b||b.debug("Failed to generate waveform: "+h)}}const toReadable=a=>{const b=new stream_1.Readable({read:()=>{}});b.push(a);b.push(null);return b};exports.toReadable=toReadable;const toBuffer=async a=>{const b=[];for await(const c of a)b.push(c);a.destroy();return Buffer.concat(b)};exports.toBuffer=toBuffer;
const getStream=async(a,b)=>{if(Buffer.isBuffer(a))return{stream:(0,exports.toReadable)(a),type:"buffer"};if("stream"in a)return{stream:a.stream,type:"readable"};const c=a.url.toString();return c.startsWith("data:")?(a=Buffer.from(c.split(",")[1],"base64"),{stream:(0,exports.toReadable)(a),type:"buffer"}):c.startsWith("http://")||c.startsWith("https://")?{stream:await (0,exports.getHttpStream)(a.url,b),type:"remote"}:{stream:(0,fs_1.createReadStream)(a.url),type:"file"}};exports.getStream=getStream;
async function generateThumbnail(a,b,c){var e;let d,h;if("image"===b){const {buffer:k,original:l}=await (0,exports.extractImageThumb)(a);d=k.toString("base64");l.width&&l.height&&(h={width:l.width,height:l.height})}else if("video"===b){b=(0,path_1.join)(getTmpFilesDirectory(),(0,generics_1.generateMessageIDV2)()+".jpg");try{await extractVideoThumb(a,b,"00:00:00",{width:32,height:32}),d=(await fs_1.promises.readFile(b)).toString("base64"),await fs_1.promises.unlink(b)}catch(k){null===(e=c.logger)||
void 0===e||e.debug("could not generate video thumb: "+k)}}return{thumbnail:d,originalImageDimensions:h}}const getHttpStream=async(a,b={})=>(await axios_1.default.get(a.toString(),{...b,responseType:"stream"})).data;exports.getHttpStream=getHttpStream;
const encryptedStream=async(a,b,{logger:c,saveOriginalFileIfRequired:e,opts:d}={})=>{var h,k;const {stream:l,type:r}=await (0,exports.getStream)(a,d);null===c||void 0===c||c.debug("fetched media stream");const m=Crypto.randomBytes(32),{cipherKey:t,iv:u,macKey:q}=await getMediaKeys(m,b),v=(0,path_1.join)(getTmpFilesDirectory(),b+(0,generics_1.generateMessageIDV2)()+"-enc"),g=(0,fs_1.createWriteStream)(v);let f,n;e&&(n=(0,path_1.join)(getTmpFilesDirectory(),b+(0,generics_1.generateMessageIDV2)()+"-original"),
f=(0,fs_1.createWriteStream)(n));b=0;e=Crypto.createCipheriv("aes-256-cbc",t,u);const p=Crypto.createHmac("sha256",q).update(u),z=Crypto.createHash("sha256"),w=Crypto.createHash("sha256");try{for await(const x of l){b+=x.length;if("remote"===r&&(null===d||void 0===d?0:d.maxContentLength)&&b+x.length>d.maxContentLength)throw new boom_1.Boom(`content length exceeded when encrypting "${r}"`,{data:{media:a,type:r}});f&&(f.write(x)||await (0,events_1.once)(f,"drain"));z.update(x);var A=e.update(x);w.update(A);
p.update(A);g.write(A)}var B=e.final();w.update(B);p.update(B);g.write(B);const y=p.digest().slice(0,10);w.update(y);const C=z.digest(),D=w.digest();g.write(y);g.end();null===(h=null===f||void 0===f?void 0:f.end)||void 0===h||h.call(f);l.destroy();null===c||void 0===c||c.debug("encrypted data successfully");return{mediaKey:m,originalFilePath:n,encFilePath:v,mac:y,fileEncSha256:D,fileSha256:C,fileLength:b}}catch(y){g.destroy();null===(k=null===f||void 0===f?void 0:f.destroy)||void 0===k||k.call(f);
e.destroy();p.destroy();z.destroy();w.destroy();l.destroy();try{await fs_1.promises.unlink(v),n&&await fs_1.promises.unlink(n)}catch(C){null===c||void 0===c||c.error({err:C},"failed deleting tmp files")}throw y;}};exports.encryptedStream=encryptedStream;const DEF_HOST="mmg.whatsapp.net",AES_CHUNK_SIZE=16,toSmallestChunkSize=a=>Math.floor(a/AES_CHUNK_SIZE)*AES_CHUNK_SIZE,getUrlFromDirectPath=a=>`https://${DEF_HOST}${a}`;exports.getUrlFromDirectPath=getUrlFromDirectPath;
const downloadContentFromMessage=async({mediaKey:a,directPath:b,url:c},e,d={})=>{b=(null===c||void 0===c?0:c.startsWith("https://mmg.whatsapp.net/"))?c:(0,exports.getUrlFromDirectPath)(b);if(!b)throw new boom_1.Boom("No valid media URL or directPath present in message",{statusCode:400});a=await getMediaKeys(a,e);return(0,exports.downloadEncryptedContent)(b,a,d)};exports.downloadContentFromMessage=downloadContentFromMessage;
const downloadEncryptedContent=async(a,{cipherKey:b,iv:c},{startByte:e,endByte:d,options:h}={})=>{let k=0,l=0,r=!1;if(e){var m=toSmallestChunkSize(e||0);m&&(l=m-AES_CHUNK_SIZE,k=m,r=!0)}m=d?toSmallestChunkSize(d||0)+AES_CHUNK_SIZE:void 0;const t={...((null===h||void 0===h?void 0:h.headers)||{}),Origin:Defaults_1.DEFAULT_ORIGIN};if(l||m)t.Range=`bytes=${l}-`,m&&(t.Range+=m);a=await (0,exports.getHttpStream)(a,{...(h||{}),headers:t,maxBodyLength:Infinity,maxContentLength:Infinity});let u=Buffer.from([]),
q;const v=(g,f)=>{e||d?(f(g.slice(k>=e?void 0:Math.max(e-k,0),k+g.length<d?void 0:Math.max(d-k,0))),k+=g.length):f(g)};h=new stream_1.Transform({transform(g,f,n){g=Buffer.concat([u,g]);f=toSmallestChunkSize(g.length);u=g.slice(f);g=g.slice(0,f);q||(f=c,r&&(f=g.slice(0,AES_CHUNK_SIZE),g=g.slice(AES_CHUNK_SIZE)),q=Crypto.createDecipheriv("aes-256-cbc",b,f),d&&q.setAutoPadding(!1));try{v(q.update(g),p=>this.push(p)),n()}catch(p){n(p)}},final(g){try{v(q.final(),f=>this.push(f)),g()}catch(f){g(f)}}});
return a.pipe(h,{end:!0})};exports.downloadEncryptedContent=downloadEncryptedContent;function extensionForMediaMessage(a){const b=Object.keys(a)[0];return"locationMessage"===b||"liveLocationMessage"===b||"productMessage"===b?".jpeg":a[b].mimetype.split(";")[0].split("/")[1]}
const getWAUploadToServer=({customUploadHosts:a,fetchAgent:b,logger:c,options:e},d)=>async(h,{mediaType:k,fileEncSha256B64:l,timeoutMs:r})=>{var m,t;let u=await d(!1),q;const v=[...a,...u.hosts];l=(0,exports.encodeBase64EncodedStringForUpload)(l);for(const {hostname:f}of v){c.debug(`uploading to "${f}"`);var g=encodeURIComponent(u.auth);g=`https://${f}${Defaults_1.MEDIA_PATH_MAP[k]}/${l}?auth=${g}&token=${l}`;let n;try{if(n=(await axios_1.default.post(g,(0,fs_1.createReadStream)(h),{...e,maxRedirects:0,
headers:{...(e.headers||{}),"Content-Type":"application/octet-stream",Origin:Defaults_1.DEFAULT_ORIGIN},httpsAgent:b,timeout:r,responseType:"json",maxBodyLength:Infinity,maxContentLength:Infinity})).data,(null===n||void 0===n?0:n.url)||(null===n||void 0===n?0:n.directPath)){q={mediaUrl:n.url,directPath:n.direct_path};break}else throw u=await d(!0),Error(`upload failed, reason: ${JSON.stringify(n)}`);}catch(p){axios_1.default.isAxiosError(p)&&(n=null===(m=p.response)||void 0===m?void 0:m.data),g=f===
(null===(t=v[u.hosts.length-1])||void 0===t?void 0:t.hostname),c.warn({trace:p.stack,uploadResult:n},`Error in uploading to ${f} ${g?"":", retrying..."}`)}}if(!q)throw new boom_1.Boom("Media upload failed on all hosts",{statusCode:500});return q};exports.getWAUploadToServer=getWAUploadToServer;
const getMediaRetryKey=a=>(0,crypto_1.hkdf)(a,32,{info:"WhatsApp Media Retry Notification"}),encryptMediaRetryRequest=async(a,b,c)=>{var e=WAProto_1.proto.ServerErrorReceipt.encode({stanzaId:a.id}).finish();const d=Crypto.randomBytes(12);b=await getMediaRetryKey(b);e=(0,crypto_1.aesEncryptGCM)(e,b,d,Buffer.from(a.id));return{tag:"receipt",attrs:{id:a.id,to:(0,WABinary_1.jidNormalizedUser)(c),type:"server-error"},content:[{tag:"encrypt",attrs:{},content:[{tag:"enc_p",attrs:{},content:e},{tag:"enc_iv",
attrs:{},content:d}]},{tag:"rmr",attrs:{jid:a.remoteJid,from_me:(!!a.fromMe).toString(),participant:a.participant||void 0}}]}};exports.encryptMediaRetryRequest=encryptMediaRetryRequest;
const decodeMediaRetryNode=a=>{var b=(0,WABinary_1.getBinaryNodeChild)(a,"rmr");b={key:{id:a.attrs.id,remoteJid:b.attrs.jid,fromMe:"true"===b.attrs.from_me,participant:b.attrs.participant}};var c=(0,WABinary_1.getBinaryNodeChild)(a,"error");c?(a=+c.attrs.code,b.error=new boom_1.Boom(`Failed to re-upload media (${a})`,{data:c.attrs,statusCode:(0,exports.getStatusCodeForMediaRetry)(a)})):(c=(0,WABinary_1.getBinaryNodeChild)(a,"encrypt"),a=(0,WABinary_1.getBinaryNodeChildBuffer)(c,"enc_p"),c=(0,WABinary_1.getBinaryNodeChildBuffer)(c,
"enc_iv"),a&&c?b.media={ciphertext:a,iv:c}:b.error=new boom_1.Boom("Failed to re-upload media (missing ciphertext)",{statusCode:404}));return b};exports.decodeMediaRetryNode=decodeMediaRetryNode;const decryptMediaRetryData=async({ciphertext:a,iv:b},c,e)=>{c=await getMediaRetryKey(c);a=(0,crypto_1.aesDecryptGCM)(a,c,b,Buffer.from(e));return WAProto_1.proto.MediaRetryNotification.decode(a)};exports.decryptMediaRetryData=decryptMediaRetryData;const getStatusCodeForMediaRetry=a=>MEDIA_RETRY_STATUS_MAP[a];
exports.getStatusCodeForMediaRetry=getStatusCodeForMediaRetry;const MEDIA_RETRY_STATUS_MAP={[WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS]:200,[WAProto_1.proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]:412,[WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]:404,[WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]:418};