aboutsummaryrefslogtreecommitdiff
path: root/README
blob: b6303a8a159f95b5dd79055c56259a62c1db54dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# -*- mode: org; org-html-head-include-default-style: nil; org-html-postamble: nil; -*-
#+OPTIONS: toc:nil
* nx-router
=nx-router= is a URL routing extension for [[https://nyxt.atlas.engineer/][Nyxt]]. It lets you define fine-grained routes so you can enhance the browsing experience without getting your attention sucked away. You can set up URL redirects, block lists, open resources with external applications, all in a cohesive configuration language.

** Installation
To install the extension, you need to download the source and place it in Nyxt's extensions path, given by the value of =nyxt-source-registry= (by default =~/.local/share/nyxt/extensions=).

#+begin_src sh
git clone https://git.sr.ht/~conses/nx-router ~/.local/share/nyxt/extensions/nx-router
#+end_src

The extension works with *Nyxt 3 onward*, so ensure to use it with the latest version of Nyxt master for the time being.

If you want to place the extension elsewhere in the system, such as for development purposes, you can configure so via the ASDF source registry mechanism. For this, you'll need to create a file in the source registry directory, =~/.config/common-lisp/source-registry.conf.d/=, and then put the following contents into it, replacing the path with the desired system path.

#+name: 10-personal-lisp.conf
#+begin_src lisp
(:tree "/path/to/user/location")
#+end_src

Then, make sure to refresh the ASDF cache via =asdf:clear-source-registry=. ASDF will now be able to find the extension on the custom path. For more information on this utility, please refer to the [[https://asdf.common-lisp.dev/asdf.html][ASDF manual]].

However, by default Nyxt won't read the custom source registry path we provided, so ensure to include a =reset-asdf-registries= invocation in the Nyxt's initialization file too.

In your Nyxt initialization file, place the following.

#+begin_src lisp
(define-nyxt-user-system-and-load nyxt-user/router
  :depends-on (nx-router)
  :components ("router.lisp"))
#+end_src

Where =router.lisp= is a custom file that should be created to provide the extension settings after the =nx-router= system has been successfully loaded. Inside this file, place the following.

#+begin_src lisp
(define-configuration web-buffer
  ((default-modes `(router:router-mode ,@%slot-value%))))
#+end_src

In addition, you should add the extension options, explained in the following section.

** Configuration
You can configure =nx-router= by customizing the mode's slots as follows.

#+begin_src lisp
(import 'router:make-route)

(define-configuration router:router-mode
  ((router:enforce-p t)
   (router:media-enabled-p t)
   (router:banner-p t)
   (router:routes '())))
#+end_src

Where =router:router-mode= slots include:

- =enforce-p= :: if non-=nil=, this prevents you from disabling the mode.
- =media-enabled-p= :: specifies whether to show media in sites. This can be overridden on a per-=router:route= basis later in the configuration.
- =banner-p= :: if non-=nil=, displays a Nyxt internal page upon visiting blocked triggers.
- =routes= :: list of =router:route='s. See the below documentation on how to build a =router:route= object.

=router:route= slots include a mini-DSL that specifies what URL part to block, redirect or invoke externally via a comparison type. Personally, I believe this is a bit more straightforward than having to fiddle around with complex regular expressions and it allows you to define many site behaviors within a single point. The following is a detailed description of all the available slots:

- =redirect= :: can take a redirect URL simply as a string or as a cons of the form =(REDIRECT-URI . TYPES)=, where =TYPES= is a property list of the form =(TYPE . RULES)= that can currently only take =:path= for =TYPE=. =RULES= is an alist of the form =(REPLACEMENT-PATH ORIGINAL-PATHS)=, where =ORIGINAL-PATHS= is a single string or list of paths of the original URL which will be redirected to =REPLACEMENT-PATH=. If you want to redirect all paths except =ORIGINAL-PATHS= to =REPLACEMENT-PATH=, prefix this list with =not=. Alternatively, it can be given as a =router:redirect= object with the appropriate slots or as a function to compute an arbitrary redirect URL.
- =blocklist= :: a property list of blocking conditions in the form of =(TYPE VALUE)=, where =TYPE= can be one of =:path= or =:host=, and =VALUE= is either another property list of the form =(TYPE PATHNAMES)=, where =TYPE= is either =:starts=, =:ends=, or =:contains= to denote the URL comparison and =PATHNAMES= is a simple string or a list of URL pathnames to draw the comparison against. You can also pass an integer as =VALUE= to indicate the number of URL /sections/ (e.g. =https://example.com/section1/section2=) to block in case the blocking condition value is not known. If =PATHNAMES= is prefixed with =not=, all paths will be blocked except for the specified list. Alternatively, a =router:blocklist= object can be supplied with the appropriate slots, or the value =t= can be passed to block all the routes for the defined triggers.
- =external= :: used to open resources externally. If it's a function, it takes a single parameter =REQUEST-DATA= and can invoke arbitrary Lisp forms within it. If provided as a string, it will run the specified command via =uiop:run-program= with the current URL as its argument in a =format=-like syntax.
- =original= :: the route's original hostname, which can be used for storage purposes (bookmarks, history, etc.) so that the original URL is recorded instead of the redirect's URL.
- =media-p= :: whether to show media in the resource or not. This is useful if you want to block all media via the =router:media-enabled-p= slot, but only override it for certain resources.
- =instances= :: you can provide a custom function to compute a list of instances, which will be added to the route's triggers automatically. This is useful if a service provides an official endpoint where these are stored.

See below for some example =router:route= routes.

- Set up all Instagram requests to redirect to the hostname =www.picuki.com= and additionally redirect all the paths that don't start with =/=, =/p/=, or =/tv/= to =/profile/= paths, and all paths that do start with =/p/= to =/media/=, as this is what the alternative front-end uses for its URL structure.

  #+begin_src lisp
  (make-route (match-regex "https://(www.)?insta.*")
              :redirect (make-instance
                         'router:redirect
                         :to "www.picuki.com"
                         :rules '(("/profile/" . (not "/" "/p/" "/tv/"))
                                  ("/media/" . "/p/")))
              :blocklist (make-instance
                          'router:blocklist
                          :rules '(:contains (not "/video"))))
  #+end_src

- Redirect all TikTok requests except the index path to =/@placeholder/video/= since this is what [[https://github.com/pablouser1/ProxiTok][ProxiTok]] uses for its video paths.

  #+begin_src lisp
  (make-route (match-domain "tiktok.com")
              :redirect '("proxitok.herokuapp.com" .
                          (:path (("/@placeholder/video/" . (not "/"))))))
  #+end_src

- Redirect all Reddit requests to the =teddit.namazso.eu= host and additionally block all of the paths belonging to this trigger except those that contain the =/comments= path. This would essentially limit the user to only being able to access Reddit publications instead of sections like its main feed.

  #+begin_src lisp
  (make-route (match-domain "reddit.com")
              :redirect "teddit.namazso.eu"
              :original "www.reddit.com"
              :blocklist '(:path (:contains (not "/comments"))))
  #+end_src

  If you pass the =:original= slot to the =route=, you can wrap Nyxt internal methods like shown below, so that history entries get recorded with the original URL.

  #+begin_src lisp
  (defmethod nyxt:on-signal-load-finished :around
      ((mode nyxt/history-mode:history-mode) url)
    (call-next-method mode (router:trace-url url)))
  #+end_src

- Matches on YouTube video URLs, videos hosted on its alternative front-ends such as [[https://invidious.io/][Invidious]], as well as MP3 files, redirecting all of these requests to =youtube.com=, and dispatching a rule which invokes an external program with the current request data, in this case launching an [[https://mpv.io/][mpv]] player IPC client process through [[https://github.com/kljohann/mpv.el][mpv.el]] to control the player from Emacs. You could also pass a one-placeholder format string such as =mpv --video=no ~a= to the =:external= slot if you'd rather not use a Lisp form, where =~a= represents the current route URL. Note how a route's trigger can also consist of a /list/ of predicates for which to match URLs, which means on the route below, it will match either URLs that contain the video Regexp or URLs that contain the =.mp3= file extension.

  #+begin_src lisp
  (make-route '((match-regex ".*/watch\\?v=.*")
                (match-file-extension "mp3"))
              :redirect "youtube.com"
              :external (lambda (data)
                          (eval-in-emacs
                           `(mpv-start
                             ,(quri:render-uri (url data))))))
  #+end_src

- The route below takes an =:instances= slot, which is a custom function like =make-invidious-instances= to be provided by the user that will compute a list of instances. This is useful if the service that is used as the =:redirect= slot offers a list of predefined instances, and these will also be added to the route's triggers on route instantiation. =:redirect= slots can also take an arbitrary function which will compute the redirect hostname to use.

  #+begin_src lisp
  (defun set-invidious-instance ()
    "Set the primary Invidious instance."
    (let ((instances
            (remove-if-not
             (lambda (instance)
               (and (string= (alex:assoc-value (second instance) :region)
                             "DE")
                    (string= (alex:assoc-value (second instance) :type)
                             "https")))
             (json:with-decoder-simple-list-semantics
               (json:decode-json-from-string
                (dex:get "https://api.invidious.io/instances.json"))))))
      (first (car instances))))

  (defun make-invidious-instances ()
    "Return a list of Invidious instances."
    (mapcar 'first
            (json:with-decoder-simple-list-semantics
              (json:decode-json-from-string
               (dex:get "https://api.invidious.io/instances.json")))))

  (make-route (match-domain "youtube.com" "youtu.be")
              :redirect 'set-invidious-instance
              :instances 'make-invidious-instances
              :blocklist '(:path (:starts "/c/")))
  #+end_src

- If you'd like to randomize your redirect hostname, you can use a service like [[https://sr.ht/~benbusby/farside/][Farside]] by including the following route, which redirects all Twitter URLs to the respective Farside endpoint.

  #+begin_src lisp
  (make-route (match-domain "twitter.com")
              :redirect (make-instance
                         'router:redirect
                         :to "farside.link"
                         :rules '(("/nitter/" . "/"))))
  #+end_src

- Showcases the use of a hostname blocklist, in this case preventing the user from accessing Amazon URLs unless they start with the =smile= hostname.

  #+begin_src lisp
  (make-route (match-domain "amazon.com")
              :blocklist '(:host (:starts (not "smile"))))
  #+end_src

- Consists of a blocklist for certain paths of the =lemmy.ml= domain; namely, the blocked paths would be those that contain =post= on them or the ones that start with =/u/=, which would block all publications and user profiles on the site.

  #+begin_src lisp
  (make-route (match-domain "lemmy.ml")
              :blocklist '(:path (:contains "post" :starts "/u/")))
  #+end_src

- Provides a combined path rule for =github.com= requests. Combined rules (specified via =or=) in paths allow you to specify two or more predicates that you wish to draw the path comparison against. In this specific combination, the integer will first indicate that we want to block those paths that consist of a single sub-section (e.g. =https://github.com/profile_name=), /or/ block all paths except the ones which contain =pulls= or =search=. This essentially allows you to specify a more general block rule and bypass it for certain scenarios. In this case, it would block all single sub-section paths on =github.com= (such as profiles, the marketplace and so on) but at the same time allow you to use GitHub's search engine and see your listed pull requests.

  #+begin_src lisp
  (make-route (match-domain "github.com")
              :blocklist '(:path (or 1 (:contains (not "pulls" "search")))))
  #+end_src

- Serves as a general blocklist trigger. To block an entire URL predicate or list of predicates, you can simply pass =t= to the =blocklist= slot.

  #+begin_src lisp
  (make-route (match-domain "timewastingsite1.com"
                            "timewastingsite2.com")
              :blocklist t)
  #+end_src

** Contributing
You can use the project's [[https://lists.sr.ht/~conses/nx-router][mailing list]] to send feedback, patches or open discussions. Bugs should be reported on the project's [[https://todo.sr.ht/~conses/nx-router][bug-tracker]].