AdGuard service (e.g. enable_url) failed

I tried to call adguard.enable_url using example data, but got error code 400, invalid url or file path.
Screenshot provided.


I am using AdGuard 2.6.1.

I also tried the script/automation from another post (Social media filter), but that does not work either.

My question is how to use adguard.enable_url. Any example or comments is appreciated.

Below are relevant logs:


Logger: homeassistant.components.websocket_api.http.connection
Source: components/adguard/__init__.py:89
Integration: Home Assistant WebSocket API (documentation, issues)
First occurred: 3:10:16 AM (3 occurrences)
Last logged: 3:10:29 AM

[2824697048] ('Failed enabling URL on AdGuard Home filter', AdGuardHomeError(400, {'message': 'invalid URL or file path\n'}))
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/adguardhome/filtering.py", line 88, in enable_url
    await self._adguard._request(
  File "/usr/local/lib/python3.8/site-packages/adguardhome/adguardhome.py", line 121, in _request
    raise AdGuardHomeError(
adguardhome.exceptions.AdGuardHomeError: (400, {'message': 'invalid URL or file path\n'})

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 135, in handle_call_service
    await hass.services.async_call(
  File "/usr/src/homeassistant/homeassistant/core.py", line 1451, in async_call
    task.result()
  File "/usr/src/homeassistant/homeassistant/core.py", line 1486, in _execute_service
    await handler.job.target(service_call)
  File "/usr/src/homeassistant/homeassistant/components/adguard/__init__.py", line 89, in enable_url
    await adguard.filtering.enable_url(call.data.get(CONF_URL))
  File "/usr/local/lib/python3.8/site-packages/adguardhome/filtering.py", line 94, in enable_url
    raise AdGuardHomeError(
adguardhome.exceptions.AdGuardHomeError: ('Failed enabling URL on AdGuard Home filter', AdGuardHomeError(400, {'message': 'invalid URL or file path\n'}))

This is an error in the add guard component
Iā€™ve changed my config to get the same result

I used add_url and remove_url as shown in your Github. It is working now. Thanks.

automation:
  - id: enable_youtube
    alias: Enable Youtube
    trigger:
      platform: state
      entity_id: input_boolean.allowyoutube
      to: 'on'
    action:
    - service: adguard.remove_url
      data:
        url: "https://raw.githubusercontent.com/gieljnssns/Social-media-Blocklists/master/adguard-youtube.txt"

  - id: disable_youtube
    alias: Disable Youtube
    trigger:
      platform: state
      entity_id: input_boolean.allowyoutube
      to: 'off'
    action:
    - service: adguard.add_url
      data:
        url: "https://raw.githubusercontent.com/gieljnssns/Social-media-Blocklists/master/adguard-youtube.txt"
        name: Youtube
3 Likes

So I have been digging into this a lot. Problem with the enable_url service is within the Adguard Library. The API has changed in the parameters it expects. I created a python script to test my suspicions.

import asyncio
import argparse
import json

from adguardhome import AdGuardHome, AdGuardHomeError

async def main(args_lst):
    """Show example how to get status of your AdGuard Home instance."""
    async with AdGuardHome("IP_ADDRESS", username='USERNAME', password='PASSWORD') as adguard:

        # Enable URL
        try:
            response = await adguard._request(
                "filtering/set_url",
                method="POST",
                json_data={
                    "url": args_lst.url,
                    "data": {
                        "name": args_lst.name,
                        "url": args_lst.url,
                        "enabled": args_lst.enable
                    },
                    "whitelist": False
                    }
            )

            # Verify Response
            response = await adguard._request(
                    "filtering/status",
                    method="GET",
                    json_data={
                        "url": args_lst.url,
                        "data": {
                            "name": args_lst.name,
                            "url": args_lst.url,
                            "enabled": args_lst.enable
                        },
                        "whitelist": False
                        }
                )

            response = json.loads(json.dumps(response))['filters']
            match = list(filter(lambda x:x["name"]==args_lst.name, response))[0]

            print('Name: "{}" Rules Enabled: {}'.format(match['name'], match['rules_count']))

        except AdGuardHomeError as exception:
            raise AdGuardHomeError(
                "Failed enabling URL on AdGuard Home filter", exception
            )

if __name__ == "__main__":

    # Set Up Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument("url")
    parser.add_argument("name")
    parser.add_argument("-e", "--enable",  action="store_true", default=False, help='Enable filter list')

    # Run logic
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main(parser.parse_args()))

It is very crude but changes will have to occur in the python package and the home assistant adguard component to pass the extra parameters in the json data.

On another note, Adguard home supports passing a local file path as a custom filter but Home Assistant will block this because it is not a valid URL (http / https).

Iā€™ve also found that the enable / disable service call doesnā€™t work, but add / remove call does.

However, what about making the service call to multiple AdGuard Home instances? I run two for redundancy. I have only one service call option to choose from despite integrating both my AdGuard instances.

Does anyone have anymore info on this? Trying to block YouTube and make a switch for it, but get unknown errors when testing via developer tools

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/adguardhome/filtering.py", line 124, in add_url
    await self._adguard.request(
  File "/usr/local/lib/python3.9/site-packages/adguardhome/adguardhome.py", line 164, in request
    raise AdGuardHomeError(
adguardhome.exceptions.AdGuardHomeError: (400, {'message': "Couldn't fetch filter from url https://www.youtube.com: data is HTML, not plain text\n"})

Entering the url as https://www.youtube.com in the url box of the developer tools.

Running HA supervised, and AdGuard is running on seperate opnsense box, The main filtering switch works fine

The integration is not flexible enough so wanted to get a different approach:
Used the ā€œClient settingsā€ which allowed to create different DNS answers for different users/children.
I used the ā€˜userā€™ as a client name and selected a default list for ā€˜Block specific servicesā€™.

Hope that would help someone, the advantage of the script is that it can be applied to any settings not just youtube.

switch:
  - platform: command_line
    switches:
      user_youtube:
        unique_id: UUID
        command_on: /config/shell/appguard-allow.sh  user youtube 
        command_off: /config/shell/appguard-deny.sh  user youtube
        command_state: /config/shell/appguard-state.sh  user youtube

/config/shell/appguard-state.sh /config/shell/appguard-allow.sh /config/shell/appguard-deny.sh ended-up being the same code.

#!/bin/bash
# echo -n "USER:PASS" | base64
PASS=BASE64;
APPGUARDHOST=IP:PORT/control

USER_TARGET=$1
APP_TARGET=$2


if [[ $0 =~ allow ]]; then

    APP_ACTION=-

elif [[ $0 =~ deny ]] ; then

    APP_ACTION=+

elif [[ $0 =~ state ]]; then 

   /usr/bin/curl -s  -X GET -m 10000 -H "Authorization: Basic $PASS" -H "Content-Type: application/json"  $APPGUARDHOST/clients | jq ' .clients[] | select(.name == "'${USER_TARGET}'") | .blocked_services | contains(["'$APP_TARGET'"])'  | grep -vq true
   exit $?

fi

# API documentation: https://github.com/AdguardTeam/AdGuardHome/blob/6f6ced33c10e2a8f5e2ca6402adb159f02a77f9b/openapi/openapi.yaml

JSON=$( /usr/bin/curl -s  -X GET -m 10000 -H "Authorization: Basic $PASS" -H "Content-Type: application/json"  $APPGUARDHOST/clients | jq ' .clients[] | select(.name == "'${USER_TARGET}'") | .blocked_services|= . '${APP_ACTION}' ["'${APP_TARGET}'"] | {"data": ., "name":.name  } ' ) 

/usr/bin/curl -s -X POST -d "${JSON}" -m 10000 -H "Authorization: Basic $PASS" -H "Content-Type: application/json"  $APPGUARDHOST/clients/update



/usr/bin/curl -s  -X GET -m 10000 -H "Authorization: Basic $PASS" -H "Content-Type: application/json"  $APPGUARDHOST/clients | jq ' .clients[] | select(.name == "'${USER_TARGET}'") | .blocked_services | contains(["'$APP_TARGET'"])'  | grep -vq true
exit $?

@avoc-adio I really like the way that you have implemented this but I have some questions so that I can better understand this and Home Assistant and Iā€™m still pretty new.

Where does the switch: yaml info go? In the configuration.yam of HA?
Where does the script go? I donā€™t see the /config/shell in my file structure, is that something that I can just make?
Is the user youtube equal to a client IP in adguard?
Inside the script, does the username and password just replace the USER:PASS? Or is there something that needs to happen before hand in order for the username and pass to be known?

Thank you for any help that you are able to give on this.

@montgomery102 apologies I did not see the reply.

The yaml information goes in configuration.yaml. If you have a bloated configuration file, you can create an include file, if you havenā€™t use yaml, I would recommend the following reading:

  • YAML - Home Assistant
    If you use the configuration editor add-on you should be able to verify the syntax. Be mindful of the leading spaces.

Regarding the /config/shell/ folder it is something Iā€™ve created, I tend to use SSH for this, if you know a bit of linux command you can do.

mkdir /config/shell
# (copy the scripts)
chmod 755 /config/shell/*sh

You can also create the folder by the file editor plugin, or via the network share.

You should obtain something like:

[core-ssh config]$ ls /config/shell/ -lha
total 20K    
drwxr-xr-x    2 root     root        4.0K Feb 16  2023 .
drwxr-xr-x   22 root     root        4.0K Jan  6 09:12 ..
-rwxr-xr-x    1 root     root        1.3K Feb 16  2023 appguard-allow.sh
-rwxr-xr-x    1 root     root        1.3K Feb 16  2023 appguard-deny.sh
-rwxr-xr-x    1 root     root        1.3K Feb 16  2023 appguard-state.sh

Regarding the question about the password, yes the USER:PASS should be replace the command should be executed in a shell. If you are not sure replace the following line

PASS=BASE64;

by these three lines:

USER="your_username"
PASSWORD="your_password"
PASS=$(echo -n "${USER}:${PASSWORD}" | base64);

Youtube is actually a category containing numerous youtube domains, the list contained in AdGuard is covering most categories you may wish, some that you can entirely disable and some that you may want to control the state ā€˜on-offā€™.

The string ā€œuserā€ in the command line is something that I configured in adguard matching a static IP address (this means that you would have to statically reserve the IP/MAC address on your router/DHCP server, or set the IP address statically on the device). You can create a different policies for each users:
ā€œSettings > Client settings > Add Clientā€

The advantage of this solution, is that you can have multiple IP addresses to a command, so you can add tablet, laptop, TVs. This type of filtering is covering OS/web browser where ad-filtering plugins are not installable - for instance adds/tracking in android apps. The method is obviously by-passable but this is really efficient for younger kids.

Regarding the command question, I think my yaml example is a bit confusing. You may want to get thi

  - platform: command_line
    switches:
      [USERNAME1]_[CATEGORY1]:
        unique_id: UUID_1
        command_on: /config/shell/appguard-allow.sh  [USERNAME1] [CATEGORY1] 
        command_off: /config/shell/appguard-deny.sh  [USERNAME1] [CATEGORY1]
        command_state: /config/shell/appguard-state.sh  [USERNAME1] [CATEGORY1]
      [USERNAME1]_CATEGORY2:
        unique_id: UUID_2
        command_on: /config/shell/appguard-allow.sh  [USERNAME1] [CATEGORY2] 
        command_off: /config/shell/appguard-deny.sh  [USERNAME1] [CATEGORY2]
        command_state: /config/shell/appguard-state.sh  [USERNAME1] [CATEGORY2]
      [USERNAME2]_CATEGORY1:
        unique_id: UUID_3
        command_on: /config/shell/appguard-allow.sh  [USERNAME2] [CATEGORY1] 
        command_off: /config/shell/appguard-deny.sh  [USERNAME2] [CATEGORY1]
        command_state: /config/shell/appguard-state.sh  [USERNAME2] [CATEGORY1]
      [USERNAME2]_CATEGORY2:
        unique_id: UUID_4
        command_on: /config/shell/appguard-allow.sh  [USERNAME2] [CATEGORY2] 
        command_off: /config/shell/appguard-deny.sh  [USERNAME2] [CATEGORY2]
        command_state: /config/shell/appguard-state.sh  [USERNAME2] [CATEGORY2]

The UUID is just an uniq string to allow HA to recognise the block as one uniq entity, you can generated it on a command line using this command if you have a linux system: uuidgen
If not on the HA cli you can use the following

[core-ssh ~]$ cat /proc/sys/kernel/random/uuid
b9257270-3707-4d92-ba7c-6dba66d9c91c
[core-ssh ~]$ cat /proc/sys/kernel/random/uuid
0f4b5fca-70c3-4a00-9a57-394195057cf8
[core-ssh ~]$ cat /proc/sys/kernel/random/uuid
14da5a19-7d13-4a77-842a-86413bc5ceb8
[core-ssh ~]$ cat /proc/sys/kernel/random/uuid
010f9dce-4191-4560-95a3-20f8edd2bfda

Overall, I would say, setup Adguard first, switch the categories manually on/off first, and then go through the switch. When the switch is created, you can be really creative on how it is use, it could be enable via a calendar, manually set, for my part I have used a switch with a timer.

An adguard integration exist, and it can give you statistics about certain type of usage such as malware related domain, which can be quite important.