Javascript nested list toggle problem when searching

the main issue is, when i search a list that is a parent and if i clicked that parent list it will not toggle the child of that list…

it should toggle the child when i clicked the matched parent list of the search result.

the search is real time it will automatically update the page.

document.addEventListener('DOMContentLoaded', function() {
  const searchBox = document.getElementById('menuSearch');

  // Toggle dropdowns manually
  document.querySelectorAll('.shopMenuList li.has-children > span').forEach(toggle => {
    toggle.addEventListener('click', function() {
      const parentLi = this.parentElement;
      parentLi.classList.toggle('open');
    });
  });

  // Recursive filter function with toggleable matched items
  function filterListItems(element, searchTerm) {
    let matchFound = false;

    // Check direct children <li>
    const children = Array.from(element.children);
    children.forEach(child => {
      if (child.tagName === 'UL') {
        const grandChildren = Array.from(child.children);
        grandChildren.forEach(grandChild => {
          const matched = filterListItems(grandChild, searchTerm);
          matchFound = matchFound || matched;
        });
      }
    });

    const text = element.textContent.toLowerCase();
    const isMatch = text.includes(searchTerm);

    const shouldShow = isMatch || matchFound;
    element.style.display = shouldShow ? '' : 'none';
    element.classList.toggle('open', matchFound); // auto-expand matching

    return shouldShow;
  }

  // Search input handler
  searchBox.addEventListener('input', function() {
    const filter = this.value.trim().toLowerCase();
    const allRootItems = document.querySelectorAll('.shopMenuList > li');

    allRootItems.forEach(item => {
      filterListItems(item, filter);
    });

    // Toggle visibility of children if any match
    document.querySelectorAll('.shopMenuList li.has-children').forEach(li => {
      const hasVisibleChild = li.querySelector('ul > li:not([style*="display: none"])');
      if (hasVisibleChild) {
        li.classList.add('open');
      } else {
        li.classList.remove('open');
      }
    });

    // Collapse all if search is empty
    if (filter === '') {
      document.querySelectorAll('.shopMenuList li.has-children').forEach(li => {
        li.classList.remove('open');
      });
    }
  });
});
/*class that gets added to toggle lists when clicked*/
.open{
  border: solid 1px;
  background: dodgerblue;  
}
<input type="text" id="menuSearch" placeholder="Search category...">
<div class="shopMenuCategory">
  <ul class="shopMenuList">
    <li class="has-children"><span>Ceramic Tiles</span>
      <ul>
        <li class="has-children"><span>Floor Tiles</span>
          <ul>
            <li class="has-children"><span>15x80</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>15x90</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>20x20</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>20x100</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>20x120</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>30x30</span>
              <ul>
                <li><span>Matte</span></li>
                <li><span>Glossy</span></li>
                <li><span>Rough</span></li>
              </ul>
            </li>
            <li class="has-children"><span>40x40</span>
              <ul>
                <li><span>Glossy</span></li>
                <li><span>Matte</span></li>
                <li><span>Rough</span></li>
              </ul>
            </li>
            <li class="has-children"><span>60x60</span>
              <ul>
                <li><span>Glossy</span></li>
                <li><span>Matte</span></li>
                <li><span>Rough</span></li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>

when i search a parent list it will only display the matched parent list and the parent of that parent list which is correct… and then when i clicked the matched parent list it will not toggle the children is the issue..

example problem

ceramic tiles
    floor tiles
        30x40 - (searched list)
            matte - (it doesnt display when the search list is toggled)

correct way

ceramic tiles
    floor tiles
        30x40 - (searched list) then (clicked to toggle the child)
            matte - (it should display)

Woocommerce Custom Payment Gateway Order Status

I am using custom Purchase Order based payment gateway method in Woocommerce and have registered my custom status status – Pending PO Verification for all orders made using this payment method. The issue is i am getting Payment Pending message on admin and customer my account-> Order page attached screenshot for the same. admin order status screenshot and customer my account->Order page screenshot . My custom order status code below:

// WooCommerce order page status add Pending PO Verification //
// 1. Register custom order status
function register_pending_po_verification_order_status() {
    register_post_status( 'wc-pending-po-verification', array(
        'label'                     => 'Pending PO Verification',
        'public'                    => true,
        'exclude_from_search'       => false,
        'show_in_admin_all_list'    => true,
        'show_in_admin_status_list' => true,
        'label_count'               => nnoop( 'Pending PO Verification (%s)', 'Pending PO Verification (%s)' )
    ));
}
add_action( 'init', 'register_pending_po_verification_order_status' );
// 2. Add to WooCommerce order status list
function add_pending_po_verification_to_order_statuses( $order_statuses ) {
    $new_order_statuses = array();
    // Insert after 'wc-pending'
    foreach ( $order_statuses as $key => $status ) {
        $new_order_statuses[ $key ] = $status;
        if ( 'wc-pending' === $key ) {
            $new_order_statuses['wc-pending-po-verification'] = 'Pending PO Verification';
        }
    }
    return $new_order_statuses;
}
add_filter( 'wc_order_statuses', 'add_pending_po_verification_to_order_statuses' );

Attached is the link https://posterika.com/op.html of my custom Purchase Order Gateway

I have tried everything to make it work but the order status isnot changing to Pending PO Verification.

Looking forward to your help.
Thanks

I have tried all the combination like renaming the order status, hooks to change the order status but it is not working. I want you to advise me where is the issue in my code. Request a solution where the order status changes to Pending PO Verification after placing the order using Purchase Order Gateway.

Performance with large API endpoint files [closed]

First some background. I am creating a RESTful API using PHP to access a MySQL database where I have stored a bunch of information I need to export to other services (3rd party software). The database isn’t especially large, and the MySQL queries aren’t intensive either.

Now here is my question: Does the size of my PHP file affect the performance of the API, in regards to both response time and server runtime?

I do not have a ton of experience with this, so I want to make sure I am not doing something wrong by creating a massive endpoint file instead of splitting my endpoint up into separate files.

Specifically I want to know at what file size does this start creating concerns, if at all.

I tried searching this topic up, but didn’t find any information on the correlation (or lack thereof) of file size to performance in regards to APIs.

How do I make PHP mail form functional? [closed]

I have like two files, both are in the same and correct folder:

index.php and contactform.php

The form appears, but I want to make if fully functional, so it actually sends an email to my email address. Can someone review the code and tell me what have I done wrong and how can I make it work, so It sends an email to [email protected]?

index.php:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
    <link="stylesheet" href="index.css"> <!-- zde doplnit odkaz na CSSka -->    
</head>
<body>
    <p>SEND E-MAIL</p>  
    <form class="contact-form" action="contactform.php" method="post">
        <input type="text" name="name" placeholder="Full name">
        <input type="text" name="mail" placeholder="Your-email">
        <input type="text" name="subject" placeholder="Subject">
        <textarea name="message" placeholder="Message"></textarea>
        <button type="submit" name="submit">SEND MAIL</button>
    </form>
</body>
</html>

contactform.php:

<?php

if (isset($_POST['submit'])) {
    $name = $_POST['name'];
    $subject = $_POST['subject'];
    $mailFrom = $_POST['mail'];
    $message = $_POST['message'];

    $mailTo = "[email protected]";
    $headers = "From: ".$mailFrom;
    $txt = "You have received an e-mail from ".$name.".nn".$message;


    mail($mailTo, $subject, $txt, $headers);
    header("Location: index.php?mailsend")
}

?>

Can you get CakePHP 4 to return JSON error responses automatically when writing an API?

CakePHP 4.x

We have an application which consists of a frontend web app (used by customers through a browser) and an API for some backend operations which is integrated with our other systems.

The code is in the expected places i.e. src/Controller/ has the Controller’s for the web app requests. In src/Controller/Api/V1/ we have our API.

The problem is if we get some error with the API such as a 404, CakePHP seems to automatically render a HTML error page.

My limited understanding of this is that in config/app.php you have this by default

'exceptionRenderer' => 'CakeErrorExceptionRenderer',

You can replace that with your own

'exceptionRenderer' => AppErrorApiErrorRenderer::class,

Then have your own custom class for it.

<?php
 // src/Error/ApiErrorRenderer.php
declare(strict_types=1);

namespace AppError;

use CakeErrorRendererWebExceptionRenderer;
use CakeHttpResponse;

class ApiErrorRenderer extends WebExceptionRenderer
{
    /**
     * Render the exception.
     *
     * @return CakeHttpResponse The response object.
     */
    public function render(): Response
    {
        $response = parent::render();

        // Set the content type to application/json for all API errors
        return $response->withType('application/json');
    }
}

The problem is that now returns a JSON response for all errors, including the 404’s for the web app. So it doesn’t render as HTML in the browser for customers, though the API now has JSON responses for it’s errors.

In phpunit it’s common to have tests that include checks such as $this->assertContentType('application/json'); before testing responses – which might include errors – from an API.

Without such changes above those tests fail because HTML rather than JSON is returned. But with the fix in place, that breaks the web app for customers.

This seems like something that should be simple and included in the framework as it’s so obvious that in both of those 2 different environments (web app vs API) you’d typically be using HTML and JSON responses respectively.

Is shift and pop reliably in an php associative array? [closed]

I have a litte program that seems to reliably use push, pop and shift in PHP on an associative array. It works on the elements in the order. But… is it reliable that is has to work this way? I need an associative array that can reliably add elements after the last one and can reliably remove the oldest element.

<?php

$a = [];
$a['first'] = '1';
$a['second'] = '2';
$a[8] = '3';
$a['forth'] = '4';
print_r($a);

print "-- now push two values --n";
$a[] = '1st pushed value';
$a[] = '2nd pushed value';
print_r($a);

print "-- now pop last --n";
array_pop($a);
print_r($a);

print "-- now shift --n";
array_shift($a);
print_r($a);

print "-- now pop last --n";
array_pop($a);
print_r($a);

?>

To clarify: yes, I’ve read the documentation, but what is the first element of an array [‘test’ => ‘t’, 0 => ‘e’]? Is it ‘test’ or is it 0? Can I rely that ‘first’ is the first element put into an array when it comes to those functions? reliable means that it is no quirk but so intended. And no, I find the documentation very vague on that.

document.activeElement.blur() behavior in firefox and chrome

Create simple html page:

<p><input type="text" /></p>
<p><input type="text" /></p>
<p><input type="text" onfocus="document.activeElement.blur();" /></p>
<p><input type="text" /></p>
<p><input type="text" /></p>

Focus on first field and click tab on your keyboard to go to the next field.

In Firefox once you reach third field, you will not go further and you will be “stuck” on second input.

In Chrome, you will skip to the fourth input field and then fifth.

How can I prevent that? I would like to either reset activeElement and start from first input field, or even have functionality from Firefox – just stop from going further.

Fiddle if you want to play with it:

https://jsfiddle.net/1r9c2abn/1/

Why is my p5.play.js not working on github but working on VScode?

Whenever I try to deploy my p5play code on GitHub Pages, it just shows a blank screen. What can I do to resolve this issue? It works perfectly on VS Code. Specifically, the page has a white background. I believe the initial code is rendering, but my game code is not. Has anyone had this experience before? I tried using a popular, recommended alternative, and it did not work either.

Website link: https://vero0888888.github.io/FeverDream/
Repository link: https://github.com/Vero0888888/FeverDream

Testing classList.contains before attempting classList.remove?

A couple days prior to this post, I asked a question about iterating children of a node versus using querySelectorAll and one of the comments pointed out that the example I made wasn’t completely fair because when I removed a class from each child element of the parent, I didn’t first test that the child’s classList contained the class to be removed.

I thought that I read (but cannot now locate the source) on MDN that this was not beneficial because the remove method would just repeat what the contains method does. My impression was that it was less efficient to do so, which, of course, differs from the comments to the mentioned question. The accepted answer to this 9+ year-old question, appears to be in agreement with what I thought I had read, stating that:

Explicitly checking is pointless. It is a waste of time and bloats your code.

However, I modified the JS Bench example (modified version https://jsben.ch/LSbrl) that was provided in a comment to my question to compare the following for that same example; and it appears that testing using contains before attempting remove is about three times quicker than just executing remove.

const c = p.children;
const l = c.length;
for (let i = 0; i < l; i++) {
  c[i].classList.remove('sel');
}
// versus
const c = p.children;
const l = c.length;
for (let i = 0, cL; i < l; i++) {
  cL = c[i].classList;
  if( cL.contains('sel') ) cL.remove('sel');
}

My question is why? Does this indicate that the two methods do not use the same code such that remove is not contains with the added step of removing the class if it is found; but contains uses other data to make the determination and in a quicker manner?

The accepted answer to my recent question states that querySelectorAll “Uses highly optimized selector matching … “. Does contains make use of the same?

There appears to be a similar relationship between the element.matches() and element.closest() methods. I asked a question about two years ago also but didn’t know enough to ask it correctly.

In many instances, I’d expect that it makes little difference and won’t be noticeable, but it would be nice to understand a bit better. Perhaps I am just slow to catch on, but I would not have expected “if contains then remove” to be quicker than just remove, from reading the documentation on MDN. And it may be an instance of early optimization that is not necessary until an issue arises.

Thank you.

how to display chapters on Youtube Using time codes?

So recently the goal was to set up Youtube chapters on the Youtube Video and the guy on the internet said you have to put timecodes with this format starting from 0:00 description etc.

this is our description over here.

This video is based on a curated/reverse prompted engineered response that was asked to Chat GPT. Instead of asking Chat GPT how to do x, we asked what projects/track would you have to go through that by going through it by default you would.

0:00 Intro to how to make clickable box in HTML
2:55 Collaborative Learning endeavors
10:00 Future Projects Overview

The question is it doesn’t show in the timeline bar though.. enter image description here

and this is the description here bellow..

enter image description here

this is the link also
https://www.youtube.com/watch?v=zYQWP8kYGOA

Chrome Omnibox suggestions not showing when async response is delayed

I’m implementing a Chrome extension using chrome.omnibox.onInputChanged.addListener with debounce and abort control. When the async operation (getKeywordSuggestions()) takes too long (5-10 seconds) due to I
need to connect with an AI server, the suggest() callback executes without errors but fails to display the dropdown.

Code are showing below.

const DEBOUNCE_DELAY = 300;
let omniboxTimer, abortController, latestInput;

chrome.omnibox.onInputChanged.addListener((input, suggest) => {
  clearTimeout(omniboxTimer);
  if (abortController) abortController.abort();
  
  abortController = new AbortController();
  latestInput = input;
  
  if (!input.trim() || input.trim().length < MIN_LENGTH) return;
  
  setDefaultSuggestion(input);
  
  omniboxTimer = setTimeout(async () => {
    if (input !== latestInput) {
      console.log(`Input changed - discarding`);
      return;
    }

    try {
      const suggestions = await getKeywordSuggestions(input); // Network call
      if (input === latestInput) {
        const formatted = suggestions.map(s => ({
          content: escapeXML(s),
          description: `${escapeXML(s)} - AI suggestion`
        }));  // still ok here
        suggest(formatted); // Not showing suggestion box
      }
    } catch (error) {
      if (input === latestInput) {
        setDefaultSuggestion('Timeout error');
        suggest([]);
      }
    }
  }, DEBOUNCE_DELAY);
});

Key observations:

The network request completes successfully

console.log confirms valid suggestions are generated

No errors appear in Chrome’s extension console

The issue only occurs when response time exceeds ~3 seconds.

What I’ve tried:

Verified latestInput check works

Find that it can work properly when I run getKeywordSuggestions() within 3s.

Environment:

Manifest V3

What I expect is that, even when there is a long wait for a response (more than 5 seconds), the “suggest” function can still operate normally.

Clicking through multiple divs in a cycle

im making a page which functions like a visual novel -esque sequence. there are four stages to it, in each you click anywhere on the page and it changes to the next stage. on the last one clicking anywhere redirects to another page on the site.
after some research, i found this post and felt like the solution was perfect.
one issue though: it messes up the centering of the image (but only the first time it shows up and not the subsequent times???) for some odd reason and i have no idea how to fix it.

i tried changing “block” to “flex” (the verse’s original display is flex) but it defaults to flex-direction:row; instead of whats specified in the css (column)?

const text = document.querySelector(".banner")
document.addEventListener('click', myFunction);

let verses = document.querySelector("#verses").children
let count = 0
function myFunction() {
  Array.from(verses).forEach(el=> el.style.display="none")
  if(count < verses.length){
    verses[count].style.display = 'block'
    count ++
    if(count===verses.length) count =0
  } 
}
//code from https://stackoverflow.com/questions/71816135/how-to-iterate-with-click-event-through-array-of-divs
body {
    background: #000000;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 1200px;
  }

ul {
   list-style-position: outside;
   list-style-type: "* ";
   margin-left:20px;
    width:100%;
}

verses{
     display: flex;
    background-color:black;
    flex-direction: column;
    justify-content:center;
    align-items: center;
    border:;
    min-height:100px;
    width:1000px;
    margin:auto;
    image-rendering: pixelated;
    gap:0px;
}

.verse1{
     display: flex;
    background-color:black;
    flex-direction: column;
    justify-content:center;
    align-items: center;
    border:;
    min-height:100px;
    width:100%;
    margin:auto;
    image-rendering: pixelated;
    gap:0px;
}

.verse2{
     display: flex;
    background-color:black;
    flex-direction: column;
    justify-content:center;
    align-items: center;
    border:;
    min-height:100px;
    width:100%;
    margin:auto;
    image-rendering: pixelated;
    gap:0px;
}

.storygraphic1{
    background-image: url("https://file.garden/ZqzaaX6Iv3i3b03H/revamp/nmnsprite.PNG");
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain; 
    min-height: 500px;
    width:80%;
    image-rendering: pixelated;
}

.storygraphic2{
    background-image: url("https://file.garden/ZqzaaX6Iv3i3b03H/revamp/nmnsprite.PNG");
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain; 
    min-height: 500px;
    width:80%;
    image-rendering: pixelated;
}

.storytextbox{
    font-family: UTDRmono;
    color:white;
    width:700px;
    border:solid white 4px;
    padding:15px;
    background-color: black;
    min-height:70px;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <link rel="stylesheet" href="WTF.css" />
        <script src="call_out.js"></script>
    </head>
    <body>
        
        
            
                <div class="banner">
                    <script src="call_out.js"></script>
                </div>
        
              <div id="verses"> 
                  
                    <div class="verse1">
                  
                        <div class="storygraphic1">
                        </div>
                    
                        <div class="storytextbox">
                            
                             <ul>
                                <li>text1</li>
                                <li>text2</li>
                            </ul> 

                        </div>
                    
                        
                    </div> <!--  verse closes  -->
                  
                  <div class="verse2" style="display: none">
                  
                        <div class="storygraphic2">
                        </div>
                    
                        <div class="storytextbox">
                            
                             <ul>
                                <li>TEXT3</li>
                                <li>TEXT4</li>
                            </ul> 

                        </div>
                   
                        
                    </div> <!--  verse closes  -->
                  
                </div> 
                
  
    </body>
</html>

QZ Tray fails to get signature and connecting

I’ve been trying to use my own certificate and private key but when I tried to implement setCertificatePromise() and setSignaturePromise() methods, my qz.websocket.connect() method does not resolve and stops my Vue App. I don’t know if I’m forgetting to implement or run other method. this all my code:

import apiClient from "./apiClient"
import { signService } from "./signService"
qz.security.setCertificatePromise(()=>{
    return signService.getCertificate()
    .then(response=>{
        const cert=response.data
        console.log(cert)
        return cert
    }).catch(err=>{
        console.error('Error en la obtención del certificado', err)
        throw err
    })
})
qz.security.setSignatureAlgorithm("SHA512");
qz.security.setSignaturePromise(function(toSign){
    console.log("Se inicia la firma del mensaje")
    return signService.signMessage(toSign)
    .then(response=>{
        console.error("message", response.data)
        return response.data
    })
    .catch(err=>{
        console.error("Error en la firma:",err)
        throw err
    })
})
qz.websocket.connect().then(()=>{
    console.log('conexion establecida')
}).catch((err)=>{
    console.error('error', err)
})
export const print=async(info)=>{
    const ESC='x1B'
    const GS='x1D'
    const alignCenter=ESC+'ax01'
    const alignLeft=ESC+'ax00'
    const alignRight=ESC+'ax02'
    const boldOn=ESC+'Ex01'
    const boldOff=ESC+'Ex00'
    const doubleHeightWidth=GS+'!x11'
    const normalSize=GS+'!x00'
    const cortar=GS+'Vx00'
    try{
        console.log('iniciar impresion')
        const printerName='POS-58'
        const config=qz.configs.create(printerName, {
            encoding:'Cp437'
        })
        console.log('creacion de ticket')
        let ticket="nn"
        ticket+=alignCenter
        ticket+=boldOn+doubleHeightWidth
        ticket+="Casa Paraíson"
        ticket+=normalSize+alignLeft
        ticket+="Dirección: Camelinas 12, Centro, 76750nTequisquiapan, Qro.nn"
        ticket+="Numero de cuenta: "+info.id+"nCliente: "+info.name+"nn"
        ticket+="Lista de productos:n-------------------n"
        ticket+="Nombre del producto   |  Precion"+boldOff
        info.products.forEach((prod)=>{
            ticket+=`${prod.product.name.padEnd(20)}$${prod.product.price.toFixed(2)}n`
        })
        ticket+=boldOn+"-------------------n"+boldOff
        ticket+=alignRight
        
        if(info.status==='open'){
            ticket+="Subtotal: $"+boldOn+info.subtotal.toFixed(2)+boldOff+"n"
            ticket+=alignCenter+doubleHeightWidth
            ticket+="Pre-ticketnnnn"+cortar
        }else{ 
            if(info.tip!==0){
                ticket+="Subtotal: $"+boldOn+info.subtotal.toFixed(2)+boldOff+"n"
                ticket+="Propina: $"+boldOn+info.tip.toFixed(2)+boldOff+"n"
            }
            ticket+="Total:"
            ticket+=boldOn+`$${info.total.toFixed(2)}nn`
            ticket+=alignCenter+doubleHeightWidth
            ticket+="Gracias pornsu compra!nnnn"+cortar
        }
        console.log('inicio con la firma')
        const data=[{type:'raw', format:'plain', data:ticket}]
        console.log("inicio de la impresion fisica")
        await qz.print(config, data)
        console.log("fin de la impresion fisica")
        return true
    }catch(err){
        console.error('Error en la impresion', err)
        return false
    }
}

setCertificatePromise() runs successfully, but after that nothing happens, any error, any confirmation, nothing.
“conexion establecida” message never appears in console.

What can I do?

TypeError: Api.payments.SendStars is not a constructor when sending Telegram Stars gift with GramJS (telegram.js)

I’m trying to write a NodeJS script using the telegram library (v2.x) to buy a Telegram Stars gift for my own user account.

I can successfully connect, log in, and fetch the list of available gifts using Api.payments.GetStarGifts. The problem occurs when I try to buy a gift using Api.payments.SendStars.

Here is a minimal, reproducible example of my code:

import "dotenv/config";
import { TelegramClient, Api } from "telegram";
import { StringSession } from "telegram/sessions/index.js";
import fs from "fs/promises";

// --- CONFIGURATION ---
// These are loaded from a .env file
const API_ID = Number(process.env.API_ID);
const API_HASH = process.env.API_HASH;
const SESSION_FILE = "./my_session.txt";

// --- MAIN SCRIPT ---
(async () => {
    // 1. Initialize session from file
    let sessionString = "";
    try {
        sessionString = await fs.readFile(SESSION_FILE, { encoding: "utf-8" });
    } catch {
        console.log("Session file not found. A new one will be created after login.");
    }

    const client = new TelegramClient(
        new StringSession(sessionString),
        API_ID,
        API_HASH,
        { connectionRetries: 5 }
    );

    // 2. Connect and save the session if it's new
    await client.connect();
    
    // This will prompt for login only if the session is invalid or missing
    if (!await client.isAuthenticated()) {
         // Handle login logic here (omitted for brevity, assume session is valid)
         console.error("Session is not valid. Please generate a session first.");
         return;
    }
    
    const me = await client.getMe();
    console.log(`Logged in as: ${me.firstName} (@${me.username})`);

    // 3. Fetch the list of gifts (this part works correctly)
    console.log("Fetching gift list...");
    const allGifts = await client.invoke(new Api.payments.GetStarGifts({}));
    const unlimitedGifts = allGifts.gifts.filter((g) => g.availabilityRemains == null);

    if (!unlimitedGifts.length) {
        console.log("No unlimited gifts found.");
        return;
    }
    const giftToBuy = unlimitedGifts[0]; // Let's just buy the first one
    console.log(`Attempting to buy gift ID: ${giftToBuy.id} for ${Number(giftToBuy.stars)} stars.`);

    // 4. Attempt to send the gift (this is where the error occurs)
    try {
        const request = new Api.payments.SendStars({
            peer: new Api.InputPeerUser({
                userId: me.id,
                accessHash: me.accessHash,
            }),
            gift: true,
            id: giftToBuy.id,
            randomId: BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)),
        });
        
        // The error is thrown on the next line
        await client.invoke(request);

        console.log("Gift purchased successfully!");
    } catch (error) {
        console.error("Failed to purchase gift:", error);
    }

    await client.disconnect();
})();

To reproduce the issue, you need a valid session string. I am generating it with a separate script and saving it to my_session.txt.

When I run this code, the script successfully logs in and fetches the gift list, but then it crashes with the following error:

Attempting to buy gift ID: 5170145012310081615 for 15 stars.
Failed to purchase gift: TypeError: Api.payments.SendStars is not a constructor
    at file:///path/to/your/project/index.js:63:25
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

I’ve double-checked the official MTProto documentation for payments.sendStars and the parameters seem correct. The GetStarGifts constructor works fine, so the Api object is being imported correctly.

What is the correct way to construct and invoke Api.payments.SendStars to send a gift to myself using a user client with the telegram library?