moonrailgun / hailuo-video-proxy

A api proxy for hailuo video proxy, which in https://hailuoai.video/
MIT License
10 stars 0 forks source link

waiting #2

Open OlegRuban-ai opened 1 week ago

OlegRuban-ai commented 1 week ago

At what stage is the progress? I can tell you that you can’t do without browser imitation, there’s a lot of protection.

OlegRuban-ai commented 1 week ago

managed to bypass requests to https://hailuoai.video/v2/api/multimodal/video/my/cursor and https://hailuoai.video/api/multimodal/video/processing

if yes, then tell me how?

by517840374 commented 6 days ago

managed to bypass requests to https://hailuoai.video/v2/api/multimodal/video/my/cursor and https://hailuoai.video/api/multimodal/video/processing

if yes, then tell me how?

I know

OlegRuban-ai commented 6 days ago

@by517840374 how did you manage to bypass this answer when submitting to image2video generation?

{"statusInfo":{"code":2,"message":"Request exception, please check request parameters","serviceTime":1729872194,"requestID":"003003f0-89f2-........a4c6","debugInfo":"2"}}

by517840374 commented 6 days ago

@by517840374 how did you manage to bypass this answer when submitting to image2video generation?

{"statusInfo":{"code":2,"message":"Request exception, please check request parameters","serviceTime":1729872194,"requestID":"003003f0-89f2-........a4c6","debugInfo":"2"}}

headers Yy

OlegRuban-ai commented 6 days ago

@by517840374

I already took this signature Yy from headers in the browser, and also created it myself in this way - it doesn’t work. Can you tell me how to form it correctly so that the request goes through?

def calculate_yy(self, endpoint: str = "", data: str = "") -> str:
    """Calculate YY signature"""
    timestamp = str(int(time.time() * 1000))
    params = self._get_query_params()
    query_string = urlencode(params)

    if endpoint:
        uri = f"{endpoint}?{query_string}"
    else:
        uri = query_string

    md5_uri = hashlib.md5(uri.encode()).hexdigest()
    md5_timestamp = hashlib.md5(timestamp.encode()).hexdigest()

    return hashlib.md5(f"{md5_uri}_{data}{md5_timestamp}ooui".encode()).hexdigest()
by517840374 commented 6 days ago

@by517840374

I already took this signature Yy from headers in the browser, and also created it myself in this way - it doesn’t work. Can you tell me how to form it correctly so that the request goes through?

def calculate_yy(self, endpoint: str = "", data: str = "") -> str:
    """Calculate YY signature"""
    timestamp = str(int(time.time() * 1000))
    params = self._get_query_params()
    query_string = urlencode(params)

    if endpoint:
        uri = f"{endpoint}?{query_string}"
    else:
        uri = query_string

    md5_uri = hashlib.md5(uri.encode()).hexdigest()
    md5_timestamp = hashlib.md5(timestamp.encode()).hexdigest()

    return hashlib.md5(f"{md5_uri}_{data}{md5_timestamp}ooui".encode()).hexdigest()

not md5,it’s Javascript,you can search yy in source

zeonagames commented 6 days ago

@by517840374

I already took this signature Yy from headers in the browser, and also created it myself in this way - it doesn’t work. Can you tell me how to form it correctly so that the request goes through?

def calculate_yy(self, endpoint: str = "", data: str = "") -> str:
    """Calculate YY signature"""
    timestamp = str(int(time.time() * 1000))
    params = self._get_query_params()
    query_string = urlencode(params)

    if endpoint:
        uri = f"{endpoint}?{query_string}"
    else:
        uri = query_string

    md5_uri = hashlib.md5(uri.encode()).hexdigest()
    md5_timestamp = hashlib.md5(timestamp.encode()).hexdigest()

    return hashlib.md5(f"{md5_uri}_{data}{md5_timestamp}ooui".encode()).hexdigest()

not md5,it’s Javascript,you can search yy in source

Hello, Can you share project ? If it works hailuoai request and respond

OlegRuban-ai commented 6 days ago

@by517840374

It is not possible to find Yy in Source or executable JS scripts an option for how Yy is generated. Could you please advise? And tell me, do you have a wallet for donations for help?

by517840374 commented 6 days ago

@by517840374

It is not possible to find Yy in Source or executable JS scripts an option for how Yy is generated. Could you please advise? And tell me, do you have a wallet for donations for help?

can you use bilibili? I dont’t have wallet.

by517840374 commented 6 days ago

@by517840374

It is not possible to find Yy in Source or executable JS scripts an option for how Yy is generated. Could you please advise? And tell me, do you have a wallet for donations for help?

微信截图_20241026005923

OlegRuban-ai commented 6 days ago

@by517840374 no, this is the first time I’ve heard about this payment method. Maybe cryptocurrency? If your parsing method helps, then I’m ready to thank you financially, of course.

by517840374 commented 6 days ago

@by517840374 no, this is the first time I’ve heard about this payment method. Maybe cryptocurrency? If your parsing method helps, then I’m ready to thank you financially, of course.

I'm chinese. I don't hava paypal. thank you very much

by517840374 commented 6 days ago

@zeonagames I cannot provide the complete code, I can only provide ideas

by517840374 commented 6 days ago
微信图片_20241026010818

then search for 75376,This is the yy generation algorithm @OlegRuban-ai @zeonagames

OlegRuban-ai commented 6 days ago

@by517840374

I rewrote that code in Python, I got YY = 2819126a786917ea4a6957d384441ca6

I substituted it into the results of headers and still when sending requests I get the error:

{"statusInfo":{"code":2,"message":"Request exception, please check request parameters","serviceTime":1729878019,"requestID":"bb98604c-6740-4156-aa1f-5ae0da87bce9","debugInfo ":"2"}}

`

def calculate_yy(self, endpoint: str, data: dict = None) -> str:
        """Calculate YY hash using method 75376"""
        try:
            # Prepare input data
            input_data = {
                "time": str(int(time.time() * 1000)),
                "body": data if data else {},
                "hasSearchParamsPath": bool(endpoint),
                "method": "POST" if data else "GET",
                "bodyToYY": None
            }
            # Convert to UTF-8 bytes
            input_bytes = json.dumps(input_data, separators=(',', ':')).encode('utf-8')
            words = self.bytes_to_words(input_bytes)

            # Process each word with bit manipulation
            for i in range(len(words)):
                word = words[i]
                words[i] = ((word << 8 | word >> 24) & 0x00FF00FF) | \
                           ((word << 24 | word >> 8) & 0xFF00FF00)

            # Add padding
            bit_length = len(input_bytes) * 8
            words.append(0x80 << (bit_length % 32))
            while (len(words) % 16) != 14:
                words.append(0)
            words.extend([bit_length & 0xffffffff, (bit_length >> 32) & 0xffffffff])

            # Initialize variables
            a = 1732584193
            b = -271733879
            c = -1732584194
            d = 271733878

            # Process each 16-word block
            for i in range(0, len(words), 16):
                aa, bb, cc, dd = a, b, c, d

                # Round 1
                a = self.ff(a, b, c, d, words[i + 0], 7, -680876936)
                d = self.ff(d, a, b, c, words[i + 1], 12, -389564586)
                c = self.ff(c, d, a, b, words[i + 2], 17, 606105819)
                b = self.ff(b, c, d, a, words[i + 3], 22, -1044525330)
                a = self.ff(a, b, c, d, words[i + 4], 7, -176418897)
                d = self.ff(d, a, b, c, words[i + 5], 12, 1200080426)
                c = self.ff(c, d, a, b, words[i + 6], 17, -1473231341)
                b = self.ff(b, c, d, a, words[i + 7], 22, -45705983)
                a = self.ff(a, b, c, d, words[i + 8], 7, 1770035416)
                d = self.ff(d, a, b, c, words[i + 9], 12, -1958414417)
                c = self.ff(c, d, a, b, words[i + 10], 17, -42063)
                b = self.ff(b, c, d, a, words[i + 11], 22, -1990404162)
                a = self.ff(a, b, c, d, words[i + 12], 7, 1804603682)
                d = self.ff(d, a, b, c, words[i + 13], 12, -40341101)
                c = self.ff(c, d, a, b, words[i + 14], 17, -1502002290)
                b = self.ff(b, c, d, a, words[i + 15], 22, 1236535329)

                # Round 2
                a = self.gg(a, b, c, d, words[i + 1], 5, -165796510)
                d = self.gg(d, a, b, c, words[i + 6], 9, -1069501632)
                c = self.gg(c, d, a, b, words[i + 11], 14, 643717713)
                b = self.gg(b, c, d, a, words[i + 0], 20, -373897302)
                a = self.gg(a, b, c, d, words[i + 5], 5, -701558691)
                d = self.gg(d, a, b, c, words[i + 10], 9, 38016083)
                c = self.gg(c, d, a, b, words[i + 15], 14, -660478335)
                b = self.gg(b, c, d, a, words[i + 4], 20, -405537848)
                a = self.gg(a, b, c, d, words[i + 9], 5, 568446438)
                d = self.gg(d, a, b, c, words[i + 14], 9, -1019803690)
                c = self.gg(c, d, a, b, words[i + 3], 14, -187363961)
                b = self.gg(b, c, d, a, words[i + 8], 20, 1163531501)
                a = self.gg(a, b, c, d, words[i + 13], 5, -1444681467)
                d = self.gg(d, a, b, c, words[i + 2], 9, -51403784)
                c = self.gg(c, d, a, b, words[i + 7], 14, 1735328473)
                b = self.gg(b, c, d, a, words[i + 12], 20, -1926607734)

                # Round 3
                a = self.hh(a, b, c, d, words[i + 5], 4, -378558)
                d = self.hh(d, a, b, c, words[i + 8], 11, -2022574463)
                c = self.hh(c, d, a, b, words[i + 11], 16, 1839030562)
                b = self.hh(b, c, d, a, words[i + 14], 23, -35309556)
                a = self.hh(a, b, c, d, words[i + 1], 4, -1530992060)
                d = self.hh(d, a, b, c, words[i + 4], 11, 1272893353)
                c = self.hh(c, d, a, b, words[i + 7], 16, -155497632)
                b = self.hh(b, c, d, a, words[i + 10], 23, -1094730640)
                a = self.hh(a, b, c, d, words[i + 13], 4, 681279174)
                d = self.hh(d, a, b, c, words[i + 0], 11, -358537222)
                c = self.hh(c, d, a, b, words[i + 3], 16, -722521979)
                b = self.hh(b, c, d, a, words[i + 6], 23, 76029189)
                a = self.hh(a, b, c, d, words[i + 9], 4, -640364487)
                d = self.hh(d, a, b, c, words[i + 12], 11, -421815835)
                c = self.hh(c, d, a, b, words[i + 15], 16, 530742520)
                b = self.hh(b, c, d, a, words[i + 2], 23, -995338651)

                # Round 4
                a = self.ii(a, b, c, d, words[i + 0], 6, -198630844)
                d = self.ii(d, a, b, c, words[i + 7], 10, 1126891415)
                c = self.ii(c, d, a, b, words[i + 14], 15, -1416354905)
                b = self.ii(b, c, d, a, words[i + 5], 21, -57434055)
                a = self.ii(a, b, c, d, words[i + 12], 6, 1700485571)
                d = self.ii(d, a, b, c, words[i + 3], 10, -1894986606)
                c = self.ii(c, d, a, b, words[i + 10], 15, -1051523)
                b = self.ii(b, c, d, a, words[i + 1], 21, -2054922799)
                a = self.ii(a, b, c, d, words[i + 8], 6, 1873313359)
                d = self.ii(d, a, b, c, words[i + 15], 10, -30611744)
                c = self.ii(c, d, a, b, words[i + 6], 15, -1560198380)
                b = self.ii(b, c, d, a, words[i + 13], 21, 1309151649)
                a = self.ii(a, b, c, d, words[i + 4], 6, -145523070)
                d = self.ii(d, a, b, c, words[i + 11], 10, -1120210379)
                c = self.ii(c, d, a, b, words[i + 2], 15, 718787259)
                b = self.ii(b, c, d, a, words[i + 9], 21, -343485551)

                # Add results back
                a = (a + aa) & 0xffffffff
                b = (b + bb) & 0xffffffff
                c = (c + cc) & 0xffffffff
                d = (d + dd) & 0xffffffff

            # Endian conversion
            result = []
            for x in [a, b, c, d]:
                result.append((x & 0xFF) << 24 | ((x >> 8) & 0xFF) << 16 | \
                              ((x >> 16) & 0xFF) << 8 | ((x >> 24) & 0xFF))

            # Final conversion to hex
            YY = ''.join(f'{x:08x}' for x in result)
            print(f"YY data: {YY}")
            return YY

        except Exception as e:
            print(f"Error calculating YY: {str(e)}")
            traceback.print_exc()
            return None`
by517840374 commented 6 days ago

@OlegRuban-ai You are amazing, with strong execution ability, but he cannot use Python to execute. just JS

by517840374 commented 6 days ago

@OlegRuban-ai py_mini_racer,I use this. example, word << 8(js) != word << 8(python)

OlegRuban-ai commented 6 days ago

@by517840374 Got it, I'll look into it, I'm not fluent in JS. Thank you.

OlegRuban-ai commented 5 days ago

@by517840374

oleg.rub.96@gmail.com

I'll be very grateful for your help. Thanks in advance!

by517840374 commented 5 days ago

@OlegRuban-ai Did you receive it?

zeonagames commented 5 days ago

@OlegRuban-ai Did you receive it?

Hello, Can you send me too ? zeonaryalcin34@gmail.com

by517840374 commented 5 days ago

@OlegRuban-ai Did you receive it?

Hello, Can you send me too ? zeonaryalcin34@gmail.com

Sorry, I have currently put it online at a price of 15$. https://www.patreon.com/bt4090

zeonagames commented 5 days ago

@OlegRuban-ai Did you receive it?

Hello, Can you send me too ? zeonaryalcin34@gmail.com

Sorry, I have currently put it online at a price of 15$. https://www.patreon.com/bt4090

Is this Hailuo api ? I dont get it what is it

by517840374 commented 5 days ago

@zeonagames Currently, the YY generation algorithm and how to construct information to generate YY

zeonagames commented 5 days ago

@zeonagames Currently, the YY generation algorithm and how to construct information to generate YY

I need completely api, please make it.

by517840374 commented 5 days ago

@zeonagames Currently, the YY generation algorithm and how to construct information to generate YY

I need completely api, please make it.

You can treat it as an API that only supports prompt issuance, and you can write the results yourself

by517840374 commented 5 days ago

@zeonagames I know how to write API. If you hire me to construct the complete API code, how much can you give me?

OlegRuban-ai commented 5 days ago

@by517840374

There are several nuances with this type of calculation.

  1. Yy is based on YY_info which includes Payload with file_id and file_name, but we don't have this data when we call https://hailuoai.video/v1/api/files/request_policy and https://hailuoai.video /v1/api/files/policy_callback

How, then, to upload an image to the site if there is Yy in the headers?

  1. Let’s say a situation: there is a local path (or URL) of an image, prompt, then how to load the image into the interface and then get the result from it? If the image is already loaded and the file_id and file_name are known, then there is no problem.
by517840374 commented 5 days ago

@by517840374

There are several nuances with this type of calculation.

  1. Yy is based on YY_info which includes Payload with file_id and file_name, but we don't have this data when we call https://hailuoai.video/v1/api/files/request_policy and https://hailuoai.video /v1/api/files/policy_callback

How, then, to upload an image to the site if there is Yy in the headers?

  1. Let’s say a situation: there is a local path (or URL) of an image, prompt, then how to load the image into the interface and then get the result from it? If the image is already loaded and the file_id and file_name are known, then there is no problem.

I know how to upload file to hailuo, I have py script

zeonagames commented 5 days ago

@by517840374 There are several nuances with this type of calculation.

  1. Yy is based on YY_info which includes Payload with file_id and file_name, but we don't have this data when we call https://hailuoai.video/v1/api/files/request_policy and https://hailuoai.video /v1/api/files/policy_callback

How, then, to upload an image to the site if there is Yy in the headers?

  1. Let’s say a situation: there is a local path (or URL) of an image, prompt, then how to load the image into the interface and then get the result from it? If the image is already loaded and the file_id and file_name are known, then there is no problem.

I know how to upload file to hailuo, I have py script

I can donate your patreon but before this I have to get api script.

OlegRuban-ai commented 4 days ago

@by517840374

can you tell me how to correctly load an image using file_id, file_name?

I get a result like this:

{'fileId': '307043509121069060', 'fileName': '1ff612e4-651e-45a5-b167-4d57cf72ad67.jpg', 'ossPath': '....'}

but after substituting the data to form yy_info I get the error: {"statusInfo":{"code":1500002,"message":"Please try again with a different prompt","serviceTime":1730061022,"requestID":"5b1af108-7de0-47c3-af16-a6a1398e028d","debugInfo ":"1500002"}}

if I substitute id and name from images that have already been loaded through the site interface, then there is no error and the generation occurs. Is it possible through Patreon if possible?

by517840374 commented 4 days ago

@by517840374

can you tell me how to correctly load an image using file_id, file_name?

I get a result like this:

{'fileId': '307043509121069060', 'fileName': '1ff612e4-651e-45a5-b167-4d57cf72ad67.jpg', 'ossPath': '....'}

but after substituting the data to form yy_info I get the error: {"statusInfo":{"code":1500002,"message":"Please try again with a different prompt","serviceTime":1730061022,"requestID":"5b1af108-7de0-47c3-af16-a6a1398e028d","debugInfo ":"1500002"}}

if I substitute id and name from images that have already been loaded through the site interface, then there is no error and the generation occurs. Is it possible through Patreon if possible?

I haven't tried it before, you can give it a try and it's highly likely to pass.

by517840374 commented 4 days ago

@by517840374

can you tell me how to correctly load an image using file_id, file_name?

I get a result like this:

{'fileId': '307043509121069060', 'fileName': '1ff612e4-651e-45a5-b167-4d57cf72ad67.jpg', 'ossPath': '....'}

but after substituting the data to form yy_info I get the error: {"statusInfo":{"code":1500002,"message":"Please try again with a different prompt","serviceTime":1730061022,"requestID":"5b1af108-7de0-47c3-af16-a6a1398e028d","debugInfo ":"1500002"}}

if I substitute id and name from images that have already been loaded through the site interface, then there is no error and the generation occurs. Is it possible through Patreon if possible?

Sorry, I didn't see the message you sent above. My script can avoid these errors, it's in that post。

OlegRuban-ai commented 4 days ago

@by517840374

the problem is that your script takes into account the formation of yy with known file id and file name, which are returned from the server. But at the stage of uploading files to the server, this data is still unknown, and policy_request and policy_callback require yy in the headers. If you substitute image data from already downloaded ones, it works, but if you need to download it, it doesn’t work.

by517840374 commented 3 days ago

@OlegRuban-ai https://www.patreon.com/posts/upload-file-114754443?utm_medium=clipboard_copy&utm_source=copyLink&utm_campaign=postshare_creator&utm_content=join_link,I'm talking about this script, not the one I sent you. It supports uploading files and obtaining file_id and file_name.

OlegRuban-ai commented 3 days ago

@by517840374

Thanks for the tip. When we send to https://hailuoai.video/api/multimodal/video/processing to check the status, should YY be generated or sent payload as for the file upload method? Judging by the answers from network, it is necessary, but it does not work properly.

And tell me, can I give you a donation on Patreon for your help?

by517840374 commented 3 days ago

@OlegRuban-ai Uploading files does not require yy, only issuing tasks and obtaining task results require constructing yy. There is a file upload script in the link I provided above.

Patreon does not have a donation function

by517840374 commented 3 days ago

@OlegRuban-ai https://afdian.com/a/bt4090,Can you open this link? This has a donation. Have you finished writing your upload file interface?

OlegRuban-ai commented 3 days ago

@by517840374

Yes, the link opens. But for some reason the payment from my country’s card does not go through. I'll try with a different card in a few hours.

Can you tell me what to do with processing? I tried it by analogy with the GET request for request_policy, where there are only parameters and headers. But it returns Request exception, please check request parameters

the link looks like: f"https://hailuoai.video/api/multimodal/video/processing?idList={video_id}&device_platform=web&app_id=3001&version_code=22201&uuid={uuid}&device_id={device_id}&os_name=Android&browser_name=chrome&device_memory=8&cpu_core_num=8& browser_language=ru&browser_platform =Linux+x86_64&screen_width=1920&screen_height=1080&unix={tt}"

Should I add yy to headers? I've tried different approaches and none have worked yet.

by517840374 commented 3 days ago

@OlegRuban-ai I don't understand where you encountered the problem

  1. yy done
  2. upload_file?
  3. Issue tasks?
  4. get results?
by517840374 commented 3 days ago

@by517840374

Yes, the link opens. But for some reason the payment from my country’s card does not go through. I'll try with a different card in a few hours.

Can you tell me what to do with processing? I tried it by analogy with the GET request for request_policy, where there are only parameters and headers. But it returns Request exception, please check request parameters

the link looks like: f"https://hailuoai.video/api/multimodal/video/processing?idList={video_id}&device_platform=web&app_id=3001&version_code=22201&uuid={uuid}&device_id={device_id}&os_name=Android&browser_name=chrome&device_memory=8&cpu_core_num=8& browser_language=ru&browser_platform =Linux+x86_64&screen_width=1920&screen_height=1080&unix={tt}"

Should I add yy to headers? I've tried different approaches and none have worked yet.

https://hailuoai.video/api/multimodal/video/processing this link is 4(get results),need yy. Or how much are you willing to pay me, and I'll help you take care of him

OlegRuban-ai commented 3 days ago

At the stage of checking the video generation status. When we have already sent it for generation, then we wait for readiness. At this stage you can check what % is ready or how much is in the queue. To do this, requests are made to processing, where an idList with video_id is passed

image

by517840374 commented 3 days ago

@OlegRuban-ai yes, It doesn't have a payload,Constructing information is similar to this: t = sha("1728659552") payload = json.dumps({}, ensure_ascii=False) yyinfo = '{}{}{}ooui'.format(url, payload, t) yy = sha(yy_info)

OlegRuban-ai commented 3 days ago

@by517840374

  1. 1728659552 - time.time{} or "000".format(int(time.time())) ?
  2. sha - function get_js_sha from previeous period for calculate SHA using JavaScript implementation ?
by517840374 commented 3 days ago

@OlegRuban-ai yes

OlegRuban-ai commented 3 days ago

@by517840374 hmmm, but it's not working image

by517840374 commented 3 days ago

@OlegRuban-ai Take a look at your yy_info and URL

OlegRuban-ai commented 3 days ago

https://hailuoai.video/api/multimodal/video/processing?idList=307278126970060800&device_platform=web&app_id=3001&version_code=22201&uuid=bafecc70-df95-407d-9656-234f917a088a&device_id=304750305378136065&os_name=Android&browser_name=chrome&device_memory=8&cpu_core_num=8&browser_language=ru&browser_platform=Linux+x86_64&screen_width=1920&screen_height=1080&unix=1730122255000

https://hailuoai.video/api/multimodal/video/processing?idList=307278126970060800&device_platform=web&app_id=3001&version_code=22201&uuid=bafecc70-df95-407d-9656-234f917a088a&device_id=304750305378136065&os_name=Android&browser_name=chrome&device_memory=8&cpu_core_num=8&browser_language=ru&browser_platform=Linux+x86_64&screen_width=1920&screen_height=1080&unix=1730122255000_{}95e4935a88ac02cdd1f7e764f6646f49ooui

empty one parameter in yy_info, before in working code i use this pattern for script: yy_info = f"{originalurl}{payload}{self.get_js_sha(tt)}ooui"

by517840374 commented 3 days ago

@OlegRuban-ai not this, example, %2Fv1%2Fapi%2Fuser%2Frenewal%3Fdevice_platform%3Dweb%26app_id%3D3001%26version_code%3D22201%26uuid%3D{UUID}%26device_id%3D{DEVICE_ID}%26os_name%3DMac%26browser_name%3Dchrome%26device_memory%3D8%26cpu_core_num%3D10%26browser_language%3Dzh-CN%26browser_platform%3DMacIntel%26screen_width%3D1920%26screenheight%3D1080%26unix%3D1730117505000{}80091aabc26c401a6963c169923a8e8fooui uuid and device_id is important,don't tell everyone

by517840374 commented 3 days ago

@OlegRuban-ai like this, %2Fapi%2Fmultimodal%2Fvideo%2Fprocessing%3FidList%3D307287641186652321%26device_platform%3Dweb%26app_id%3D3001%26version_code%3D22201%26uuid%3D{UUID}%26device_id%3D{DEVICE_ID}%26os_name%3DWindows%26browser_name%3Dchrome%26device_memory%3D8%26cpu_core_num%3D20%26browser_language%3Dzh-CN%26browser_platform%3DWin32%26screen_width%3D1536%26screenheight%3D864%26unix%3D1730122749000{}28d5d34adba1844c547357dda10d16d1ooui