Compare commits

...

10 Commits

Author SHA1 Message Date
c6f1156bc6 changed approach, got rid of settings.yml binding entirely
Some checks failed
Documentation / Release (push) Has been cancelled
Integration / Python 3.10 (push) Has been cancelled
Integration / Python 3.11 (push) Has been cancelled
Integration / Python 3.12 (push) Has been cancelled
Integration / Python 3.13 (push) Has been cancelled
Integration / Python 3.9 (push) Has been cancelled
Integration / Theme (push) Has been cancelled
Integration / Docker (push) Has been cancelled
2025-05-10 22:50:37 -05:00
cf5dbbbc77 fixed...? 2025-05-10 22:45:56 -05:00
8477fb4846 oh, portainer didn't like that one 2025-05-10 22:43:33 -05:00
8204f4ee72 sets the config volume to read-only 2025-05-10 22:37:36 -05:00
9d9a0fe95e reverted 2025-05-10 22:31:02 -05:00
427a84d823 attempt #2 2025-05-10 22:29:01 -05:00
e971a3d7ab experimental mobile touchscreen support for oneko 2025-05-10 22:20:36 -05:00
89cb563793 fixed issue where oneko.gif was not referenced correctly, causing invisible neko 2025-05-10 21:51:46 -05:00
ac5c83b87d fixed typo in docker compose 2025-05-10 21:45:51 -05:00
34437e8491 implemented oneko 2025-05-10 21:41:29 -05:00
11 changed files with 913 additions and 228 deletions

View File

@ -4,6 +4,4 @@ services:
pull_policy: build pull_policy: build
restart: unless-stopped restart: unless-stopped
ports: ports:
- "${NEKOSEARCH_PORT]:8080" - "${NEKOSEARCH_PORT}:8080"
volumes:
- ./nekosearch_config:/etc/searxng

View File

@ -2,7 +2,7 @@ general:
# Debug mode, only for development. Is overwritten by ${SEARXNG_DEBUG} # Debug mode, only for development. Is overwritten by ${SEARXNG_DEBUG}
debug: false debug: false
# displayed name # displayed name
instance_name: "SearXNG" instance_name: "nekosearch"
# For example: https://example.com/privacy # For example: https://example.com/privacy
privacypolicy_url: false privacypolicy_url: false
# use true to use your own donation page written in searx/info/en/donate.md # use true to use your own donation page written in searx/info/en/donate.md
@ -32,16 +32,16 @@ brand:
search: search:
# Filter results. 0: None, 1: Moderate, 2: Strict # Filter results. 0: None, 1: Moderate, 2: Strict
safe_search: 0 safe_search: 2
# Existing autocomplete backends: "360search", "baidu", "brave", "dbpedia", "duckduckgo", "google", "yandex", # Existing autocomplete backends: "360search", "baidu", "brave", "dbpedia", "duckduckgo", "google", "yandex",
# "mwmbl", "seznam", "sogou", "stract", "swisscows", "quark", "qwant", "wikipedia" - # "mwmbl", "seznam", "sogou", "stract", "swisscows", "qwant", "wikipedia" -
# leave blank to turn it off by default. # leave blank to turn it off by default.
autocomplete: "" autocomplete: "duckduckgo"
# minimun characters to type before autocompleter starts # minimun characters to type before autocompleter starts
autocomplete_min: 4 autocomplete_min: 4
# backend for the favicon near URL in search results. # backend for the favicon near URL in search results.
# Available resolvers: "allesedv", "duckduckgo", "google", "yandex" - leave blank to turn it off by default. # Available resolvers: "allesedv", "duckduckgo", "google", "yandex" - leave blank to turn it off by default.
favicon_resolver: "" favicon_resolver: "duckduckgo"
# Default search language - leave blank to detect from browser information or # Default search language - leave blank to detect from browser information or
# use codes from 'languages.py' # use codes from 'languages.py'
default_lang: "auto" default_lang: "auto"
@ -83,8 +83,8 @@ server:
port: 8888 port: 8888
bind_address: "127.0.0.1" bind_address: "127.0.0.1"
# public URL of the instance, to ensure correct inbound links. Is overwritten # public URL of the instance, to ensure correct inbound links. Is overwritten
# by ${SEARXNG_BASE_URL}. # by ${SEARXNG_URL}.
base_url: false # "http://example.com/location" base_url: https://nekosear.ch # "http://example.com/location"
# rate limit the number of request on the instance, block some bots. # rate limit the number of request on the instance, block some bots.
# Is overwritten by ${SEARXNG_LIMITER} # Is overwritten by ${SEARXNG_LIMITER}
limiter: false limiter: false
@ -95,14 +95,13 @@ server:
# If your instance owns a /etc/searxng/settings.yml file, then set the following # If your instance owns a /etc/searxng/settings.yml file, then set the following
# values there. # values there.
secret_key: "ultrasecretkey" # Is overwritten by ${SEARXNG_SECRET} secret_key: "a9b0aafe71f3dbe131d66762f03e27ab02dfa409541184f3d34036926d7dd79e" # Is overwritten by ${SEARXNG_SECRET}
# Proxy image results through SearXNG. Is overwritten by ${SEARXNG_IMAGE_PROXY} # Proxy image results through SearXNG. Is overwritten by ${SEARXNG_IMAGE_PROXY}
image_proxy: false image_proxy: false
# 1.0 and 1.1 are supported # 1.0 and 1.1 are supported
http_protocol_version: "1.0" http_protocol_version: "1.0"
# POST queries are more secure as they don't show up in history but may cause # POST queries are more secure as they don't show up in history but may cause
# problems when using Firefox containers. # problems when using Firefox containers
# Is overwritten by ${SEARXNG_METHOD}
method: "POST" method: "POST"
default_http_headers: default_http_headers:
X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff
@ -124,13 +123,13 @@ ui:
templates_path: "" templates_path: ""
# query_in_title: When true, the result page's titles contains the query # query_in_title: When true, the result page's titles contains the query
# it decreases the privacy, since the browser can records the page titles. # it decreases the privacy, since the browser can records the page titles.
query_in_title: false query_in_title: true
# infinite_scroll: When true, automatically loads the next page when scrolling to bottom of the current page. # infinite_scroll: When true, automatically loads the next page when scrolling to bottom of the current page.
infinite_scroll: false infinite_scroll: true
# ui theme # ui theme
default_theme: simple default_theme: simple
# center the results ? # center the results ?
center_alignment: false center_alignment: true
# URL prefix of the internet archive, don't forget trailing slash (if needed). # URL prefix of the internet archive, don't forget trailing slash (if needed).
# cache_url: "https://webcache.googleusercontent.com/search?q=cache:" # cache_url: "https://webcache.googleusercontent.com/search?q=cache:"
# Default interface locale - leave blank to detect from browser information or # Default interface locale - leave blank to detect from browser information or
@ -140,7 +139,7 @@ ui:
# results_on_new_tab: false # results_on_new_tab: false
theme_args: theme_args:
# style of simple theme: auto, light, dark # style of simple theme: auto, light, dark
simple_style: auto simple_style: dark
# Perform search immediately if a category selected. # Perform search immediately if a category selected.
# Disable to select multiple categories at once and start the search manually. # Disable to select multiple categories at once and start the search manually.
search_on_category_select: true search_on_category_select: true
@ -152,7 +151,7 @@ ui:
# Lock arbitrary settings on the preferences page. # Lock arbitrary settings on the preferences page.
# #
# preferences: # preferences:
# lock: # lock:
# - categories # - categories
# - language # - language
# - autocomplete # - autocomplete
@ -208,8 +207,16 @@ outgoing:
# are also supported: see # are also supported: see
# https://2.python-requests.org/en/latest/user/advanced/#socks # https://2.python-requests.org/en/latest/user/advanced/#socks
# #
# proxies: proxies:
# all://: all://:
- http://38.33.146.13:2000
- http://38.33.148.3:2000
- http://38.38.254.150:2000
- http://38.33.146.96:2000
#https: http://192.168.1.78:8888
#proxies:
#all://:
#- http://192.168.1.78:8888
# - http://proxy1:8080 # - http://proxy1:8080
# - http://proxy2:8080 # - http://proxy2:8080
# #
@ -217,7 +224,7 @@ outgoing:
# #
# Extra seconds to add in order to account for the time taken by the proxy # Extra seconds to add in order to account for the time taken by the proxy
# #
# extra_proxy_timeout: 10 # extra_proxy_timeout: 20
# #
# uncomment below section only if you have more than one network interface # uncomment below section only if you have more than one network interface
# which can be the source of outgoing search requests # which can be the source of outgoing search requests
@ -227,38 +234,29 @@ outgoing:
# - 1.1.1.2 # - 1.1.1.2
# - fe80::/126 # - fe80::/126
# Plugin configuration, for more details see # External plugin configuration, for more details see
# https://docs.searxng.org/admin/settings/settings_plugins.html # https://docs.searxng.org/admin/settings/settings_plugins.html
# #
plugins: # plugins:
# - mypackage.mymodule.MyPlugin
searx.plugins.calculator.SXNGPlugin: # - mypackage.mymodule.MyOtherPlugin
active: true # - ...
searx.plugins.hash_plugin.SXNGPlugin:
active: true
searx.plugins.self_info.SXNGPlugin:
active: true
searx.plugins.unit_converter.SXNGPlugin:
active: true
searx.plugins.ahmia_filter.SXNGPlugin:
active: true
searx.plugins.hostnames.SXNGPlugin:
active: true
searx.plugins.oa_doi_rewrite.SXNGPlugin:
active: false
searx.plugins.tor_check.SXNGPlugin:
active: false
searx.plugins.tracker_url_remover.SXNGPlugin:
active: false
# Comment or un-comment plugin to activate / deactivate by default.
# https://docs.searxng.org/admin/settings/settings_plugins.html
#
# enabled_plugins:
# # these plugins are enabled if nothing is configured ..
# - 'Basic Calculator'
# - 'Hash plugin'
# - 'Self Information'
# - 'Tracker URL remover'
# - 'Unit converter plugin'
# - 'Ahmia blacklist' # activation depends on outgoing.using_tor_proxy
# # these plugins are disabled if nothing is configured ..
# - 'Hostnames plugin' # see 'hostnames' configuration below
# - 'Open Access DOI rewrite'
# - 'Tor check plugin'
# Configuration of the "Hostnames plugin": # Configuration of the "Hostnames plugin":
# #
@ -362,11 +360,6 @@ engines:
shortcut: 9g shortcut: 9g
disabled: true disabled: true
- name: acfun
engine: acfun
shortcut: acf
disabled: true
- name: adobe stock - name: adobe stock
engine: adobe_stock engine: adobe_stock
shortcut: asi shortcut: asi
@ -434,11 +427,6 @@ engines:
disabled: true disabled: true
shortcut: aa shortcut: aa
- name: ansa
engine: ansa
shortcut: ans
disabled: true
# - name: annas articles # - name: annas articles
# engine: annas_archive # engine: annas_archive
# shortcut: aaa # shortcut: aaa
@ -495,6 +483,7 @@ engines:
engine: artic engine: artic
shortcut: arc shortcut: arc
timeout: 4.0 timeout: 4.0
disabled: true
- name: arxiv - name: arxiv
engine: arxiv engine: arxiv
@ -516,27 +505,6 @@ engines:
shortcut: bc shortcut: bc
categories: music categories: music
- name: baidu
baidu_category: general
categories: [general]
engine: baidu
shortcut: bd
disabled: true
- name: baidu images
baidu_category: images
categories: [images]
engine: baidu
shortcut: bdi
disabled: true
- name: baidu kaifa
baidu_category: it
categories: [it]
engine: baidu
shortcut: bdk
disabled: true
- name: wikipedia - name: wikipedia
engine: wikipedia engine: wikipedia
shortcut: wp shortcut: wp
@ -552,7 +520,6 @@ engines:
- name: bing - name: bing
engine: bing engine: bing
shortcut: bi shortcut: bi
disabled: true
- name: bing images - name: bing images
engine: bing_images engine: bing_images
@ -566,11 +533,6 @@ engines:
engine: bing_videos engine: bing_videos
shortcut: biv shortcut: biv
- name: bitchute
engine: bitchute
shortcut: bit
disabled: true
- name: bitbucket - name: bitbucket
engine: xpath engine: xpath
paging: true paging: true
@ -619,38 +581,6 @@ engines:
# to show premium or plus results too: # to show premium or plus results too:
# skip_premium: false # skip_premium: false
# WARNING: links from chinaso.com voilate users privacy
# Before activate these engines its mandatory to read
# - https://github.com/searxng/searxng/issues/4694
# - https://docs.searxng.org/dev/engines/online/chinaso.html
- name: chinaso news
engine: chinaso
shortcut: chinaso
categories: [news]
chinaso_category: news
chinaso_news_source: all
disabled: true
inactive: true
- name: chinaso images
engine: chinaso
network: chinaso news
shortcut: chinasoi
categories: [images]
chinaso_category: images
disabled: true
inactive: true
- name: chinaso videos
engine: chinaso
network: chinaso news
shortcut: chinasov
categories: [videos]
chinaso_category: videos
disabled: true
inactive: true
- name: cloudflareai - name: cloudflareai
engine: cloudflareai engine: cloudflareai
shortcut: cfai shortcut: cfai
@ -725,6 +655,26 @@ engines:
search_type: news search_type: news
disabled: true disabled: true
- name: curlie
engine: xpath
shortcut: cl
categories: general
disabled: true
paging: true
lang_all: ''
search_url: https://curlie.org/search?q={query}&lang={lang}&start={pageno}&stime=92452189
page_size: 20
results_xpath: //div[@id="site-list-content"]/div[@class="site-item"]
url_xpath: ./div[@class="title-and-desc"]/a/@href
title_xpath: ./div[@class="title-and-desc"]/a/div
content_xpath: ./div[@class="title-and-desc"]/div[@class="site-descr"]
about:
website: https://curlie.org/
wikidata_id: Q60715723
use_official_api: false
require_api_key: false
results: HTML
- name: currency - name: currency
engine: currency_convert engine: currency_convert
categories: general categories: general
@ -744,6 +694,7 @@ engines:
engine: deviantart engine: deviantart
shortcut: da shortcut: da
timeout: 3.0 timeout: 3.0
disabled: true
- name: ddg definitions - name: ddg definitions
engine: duckduckgo_definitions engine: duckduckgo_definitions
@ -803,18 +754,17 @@ engines:
results: HTML results: HTML
# - name: elasticsearch # - name: elasticsearch
# shortcut: els # shortcut: es
# engine: elasticsearch # engine: elasticsearch
# base_url: http://localhost:9200 # base_url: http://localhost:9200
# username: elastic # username: elastic
# password: changeme # password: changeme
# index: my-index # index: my-index
# enable_http: true
# # available options: match, simple_query_string, term, terms, custom # # available options: match, simple_query_string, term, terms, custom
# query_type: match # query_type: match
# # if query_type is set to custom, provide your query here # # if query_type is set to custom, provide your query here
# # custom_query_json: {"query":{"match_all": {}}} # #custom_query_json: {"query":{"match_all": {}}}
# # show_metadata: false # #show_metadata: false
# disabled: true # disabled: true
- name: wikidata - name: wikidata
@ -925,6 +875,7 @@ engines:
# api_key: 'apikey' # required! # api_key: 'apikey' # required!
# Or you can use the html non-stable engine, activated by default # Or you can use the html non-stable engine, activated by default
engine: flickr_noapi engine: flickr_noapi
disabled: true
- name: free software directory - name: free software directory
engine: mediawiki engine: mediawiki
@ -1013,11 +964,11 @@ engines:
engine: goodreads engine: goodreads
shortcut: good shortcut: good
timeout: 4.0 timeout: 4.0
disabled: true
- name: google - name: google
engine: google engine: google
shortcut: go shortcut: go
disabled: true
# additional_tests: # additional_tests:
# android: *test_android # android: *test_android
@ -1126,27 +1077,6 @@ engines:
require_api_key: false require_api_key: false
results: JSON results: JSON
- name: il post
engine: il_post
shortcut: pst
- name: huggingface
engine: huggingface
shortcut: hf
disabled: true
- name: huggingface datasets
huggingface_endpoint: datasets
engine: huggingface
shortcut: hfd
disabled: true
- name: huggingface spaces
huggingface_endpoint: spaces
engine: huggingface
shortcut: hfs
disabled: true
- name: imdb - name: imdb
engine: imdb engine: imdb
shortcut: imdb shortcut: imdb
@ -1180,11 +1110,6 @@ engines:
shortcut: ip shortcut: ip
disabled: true disabled: true
- name: iqiyi
engine: iqiyi
shortcut: iq
disabled: true
- name: jisho - name: jisho
engine: jisho engine: jisho
shortcut: js shortcut: js
@ -1333,19 +1258,12 @@ engines:
disabled: true disabled: true
number_of_results: 20 number_of_results: 20
# https://docs.searxng.org/dev/engines/offline/search-indexer-engines.html#module-searx.engines.meilisearch
# - name: meilisearch # - name: meilisearch
# engine: meilisearch # engine: meilisearch
# shortcut: mes # shortcut: mes
# enable_http: true # enable_http: true
# base_url: http://localhost:7700 # base_url: http://localhost:7700
# index: my-index # index: my-index
# auth_key: Bearer XXXX
- name: microsoft learn
engine: microsoft_learn
shortcut: msl
disabled: true
- name: mixcloud - name: mixcloud
engine: mixcloud engine: mixcloud
@ -1382,11 +1300,6 @@ engines:
shortcut: mwm shortcut: mwm
disabled: true disabled: true
- name: niconico
engine: niconico
shortcut: nico
disabled: true
- name: npm - name: npm
engine: npm engine: npm
shortcut: npm shortcut: npm
@ -1414,32 +1327,20 @@ engines:
require_api_key: false require_api_key: false
results: JSON results: JSON
# https://docs.searxng.org/dev/engines/online/mullvad_leta.html # read https://docs.searxng.org/dev/engines/online/mullvad_leta.html
- name: mullvadleta # - name: mullvadleta
engine: mullvad_leta # engine: mullvad_leta
disabled: true # leta_engine: google # choose one of the following: google, brave
leta_engine: google # use_cache: true # Only 100 non-cache searches per day, suggested only for private instances
categories: [general, web] # search_url: https://leta.mullvad.net
shortcut: ml # categories: [general, web]
# shortcut: ml
- name: mullvadleta brave
engine: mullvad_leta
network: mullvadleta
disabled: true
leta_engine: brave
categories: [general, web]
shortcut: mlb
- name: odysee - name: odysee
engine: odysee engine: odysee
shortcut: od shortcut: od
disabled: true disabled: true
- name: ollama
engine: ollama
shortcut: ollama
disabled: true
- name: openairedatasets - name: openairedatasets
engine: json_engine engine: json_engine
paging: true paging: true
@ -1701,24 +1602,11 @@ engines:
shortcut: pypi shortcut: pypi
engine: pypi engine: pypi
- name: quark
quark_category: general
categories: [general]
engine: quark
shortcut: qk
disabled: true
- name: quark images
quark_category: images
categories: [images]
engine: quark
shortcut: qki
disabled: true
- name: qwant - name: qwant
qwant_categ: web qwant_categ: web
engine: qwant engine: qwant
shortcut: qw shortcut: qw
disabled: true
categories: [general, web] categories: [general, web]
additional_tests: additional_tests:
rosebud: *test_rosebud rosebud: *test_rosebud
@ -1776,12 +1664,6 @@ engines:
page_size: 25 page_size: 25
disabled: true disabled: true
- name: reuters
engine: reuters
shortcut: reu
# https://docs.searxng.org/dev/engines/online/reuters.html
# sort_order = "relevance"
- name: right dao - name: right dao
engine: xpath engine: xpath
paging: true paging: true
@ -1836,12 +1718,6 @@ engines:
about: about:
website: https://searchmysite.net website: https://searchmysite.net
- name: selfhst icons
engine: selfhst
shortcut: si
inactive: true
disabled: true
- name: sepiasearch - name: sepiasearch
engine: sepiasearch engine: sepiasearch
shortcut: sep shortcut: sep
@ -1972,10 +1848,6 @@ engines:
startpage_categ: images startpage_categ: images
categories: [images, web] categories: [images, web]
shortcut: spi shortcut: spi
- name: steam
engine: steam
shortcut: stm
disabled: true disabled: true
- name: tokyotoshokan - name: tokyotoshokan
@ -1995,15 +1867,15 @@ engines:
# For this demo of the sqlite engine download: # For this demo of the sqlite engine download:
# https://liste.mediathekview.de/filmliste-v2.db.bz2 # https://liste.mediathekview.de/filmliste-v2.db.bz2
# and unpack into searx/data/filmliste-v2.db # and unpack into searx/data/filmliste-v2.db
# Query to test: "!mediathekview concert" # Query to test: "!demo concert"
# #
# - name: mediathekview # - name: demo
# engine: sqlite # engine: sqlite
# shortcut: mediathekview # shortcut: demo
# categories: [general, videos] # categories: general
# result_type: MainResult # result_template: default.html
# database: searx/data/filmliste-v2.db # database: searx/data/filmliste-v2.db
# query_str: >- # query_str: >-
# SELECT title || ' (' || time(duration, 'unixepoch') || ')' AS title, # SELECT title || ' (' || time(duration, 'unixepoch') || ')' AS title,
# COALESCE( NULLIF(url_video_hd,''), NULLIF(url_video_sd,''), url_video) AS url, # COALESCE( NULLIF(url_video_hd,''), NULLIF(url_video_sd,''), url_video) AS url,
# description AS content # description AS content
@ -2137,7 +2009,6 @@ engines:
content_query: Snippet content_query: Snippet
categories: [general, web] categories: [general, web]
shortcut: wib shortcut: wib
disabled: true
about: about:
website: https://wiby.me/ website: https://wiby.me/
@ -2346,8 +2217,8 @@ engines:
- name: mojeek - name: mojeek
shortcut: mjk shortcut: mjk
engine: mojeek engine: mojeek
categories: [general, web]
disabled: true disabled: true
categories: [general, web]
- name: mojeek images - name: mojeek images
shortcut: mjkimg shortcut: mjkimg
@ -2381,7 +2252,6 @@ engines:
content_xpath: //div[@class="total_dsc_wrap"]/a content_xpath: //div[@class="total_dsc_wrap"]/a
first_page_num: 1 first_page_num: 1
page_size: 10 page_size: 10
disabled: true
about: about:
website: https://www.naver.com/ website: https://www.naver.com/
wikidata_id: Q485639 wikidata_id: Q485639
@ -2670,12 +2540,6 @@ engines:
shortcut: pgo shortcut: pgo
disabled: true disabled: true
- name: senscritique
engine: senscritique
shortcut: scr
timeout: 4.0
disabled: true
# Doku engine lets you access to any Doku wiki instance: # Doku engine lets you access to any Doku wiki instance:
# A public one or a privete/corporate one. # A public one or a privete/corporate one.
# - name: ubuntuwiki # - name: ubuntuwiki

View File

@ -0,0 +1,242 @@
function neko() {
var nekoEl = document.createElement("div");
var nekoPosX = 32;
var nekoPosY = 32;
var mousePosX = 0;
var mousePosY = 0;
var frameCount = 0;
var idleTime = 0;
var idleAnimation = null;
var idleAnimationFrame = 0;
var direction;
var IE = document.all ? true : false;
var nekoSpeed = 10;
var spriteSets = {
idle: [[-3, -3]],
alert: [[-7, -3]],
scratchSelf: [
[-5, 0],
[-6, 0],
[-7, 0],
],
scratchWallN: [
[0, 0],
[0, -1],
],
scratchWallS: [
[-7, -1],
[-6, -2],
],
scratchWallE: [
[-2, -2],
[-2, -3],
],
scratchWallW: [
[-4, 0],
[-4, -1],
],
tired: [[-3, -2]],
sleeping: [
[-2, 0],
[-2, -1],
],
N: [
[-1, -2],
[-1, -3],
],
NE: [
[0, -2],
[0, -3],
],
E: [
[-3, 0],
[-3, -1],
],
SE: [
[-5, -1],
[-5, -2],
],
S: [
[-6, -3],
[-7, -2],
],
SW: [
[-5, -3],
[-6, -1],
],
W: [
[-4, -2],
[-4, -3],
],
NW: [
[-1, 0],
[-1, -1],
],
};
function init() {
nekoEl.id = "oneko";
nekoEl.ariaHidden = true;
nekoEl.style.width = "32px";
nekoEl.style.height = "32px";
nekoEl.style.position = "absolute";
nekoEl.style.pointerEvents = "none";
nekoEl.style.backgroundImage = "url('oneko.gif')";
nekoEl.style.imageRendering = "pixelated";
nekoEl.style.left = nekoPosX - 16 + "px";
nekoEl.style.top = nekoPosY - 16 + "px";
nekoEl.style.zIndex = Number.MAX_VALUE;
document.body.appendChild(nekoEl);
function mousePos(event) {
if (IE) {
event = window.event;
}
mousePosX = event.clientX;
mousePosY = event.clientY - 16;
}
document.onmousemove = mousePos;
window.onekoInterval = setInterval(frame, 100);
}
function setSprite(name, frame) {
var length = spriteSets[name].length;
if (IE) {
length = 0;
// Internet explorer is really fucking dumb
while (length < spriteSets[name].length) {
if (spriteSets[name][length] != null) {
length = length + 1;
continue;
}
break;
}
}
var sprite = spriteSets[name][frame % length];
nekoEl.style.backgroundPosition =
sprite["0"] * 32 + "px " + sprite["1"] * 32 + "px";
}
function resetIdleAnimation() {
idleAnimation = null;
idleAnimationFrame = 0;
}
function idle() {
idleTime = idleTime + 1;
// every ~ 20 seconds
if (
idleTime > 10 &&
Math.floor(Math.random() * 200) == 0 &&
idleAnimation == null
) {
var avalibleIdleAnimations = ["sleeping", "scratchSelf"];
if (nekoPosX < 32) {
avalibleIdleAnimations.push("scratchWallW");
}
if (nekoPosY < 32) {
avalibleIdleAnimations.push("scratchWallN");
}
if (nekoPosX > window.innerWidth - 32) {
avalibleIdleAnimations.push("scratchWallE");
}
if (nekoPosY > window.innerHeight - 32) {
avalibleIdleAnimations.push("scratchWallS");
}
idleAnimation =
avalibleIdleAnimations[
Math.floor(Math.random() * avalibleIdleAnimations.length)
];
}
switch (idleAnimation) {
case "sleeping":
if (idleAnimationFrame < 8) {
setSprite("tired", 0);
break;
}
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
if (idleAnimationFrame > 192) {
resetIdleAnimation();
}
break;
case "scratchWallN":
case "scratchWallS":
case "scratchWallE":
case "scratchWallW":
case "scratchSelf":
setSprite(idleAnimation, idleAnimationFrame);
if (idleAnimationFrame > 9) {
resetIdleAnimation();
}
break;
default:
setSprite("idle", 0);
return;
}
idleAnimationFrame = idleAnimationFrame + 1;
}
function frame() {
frameCount = frameCount + 1;
var diffX = nekoPosX - mousePosX;
var diffY = nekoPosY - mousePosY;
var distance = Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2));
if (distance < nekoSpeed || distance < 48) {
idle();
return;
}
idleAnimation = null;
idleAnimationFrame = 0;
if (idleTime > 1) {
setSprite("alert", 0);
// count down after being alerted before moving
idleTime = Math.min(idleTime, 7);
idleTime = idleTime - 1;
return;
}
direction = "";
if (diffY / distance > 0.5) {
direction = "N";
} else if (diffY / distance < -0.5) {
direction = "S";
}
if (diffX / distance > 0.5) {
direction = direction + "W";
} else if (diffX / distance < -0.5) {
direction = direction + "E";
}
setSprite(direction, frameCount);
if (distance > nekoSpeed) {
nekoPosX = nekoPosX - (diffX / distance) * nekoSpeed;
nekoPosY = nekoPosY - (diffY / distance) * nekoSpeed;
} else {
nekoPosX = mousePosX;
nekoPosY = mousePosY;
}
nekoPosX = Math.min(
Math.max(16, nekoPosX),
document.getElementsByTagName("body")[0].clientWidth - 16
);
nekoPosY = Math.min(
Math.max(16, nekoPosY),
document.getElementsByTagName("body")[0].clientHeight - 16
);
nekoEl.style.left = nekoPosX - 16 + "px";
nekoEl.style.top = nekoPosY - 16 + "px";
}
init();
}
neko();

View File

@ -0,0 +1,291 @@
// oneko.js: https://github.com/adryd325/oneko.js (webring variant)
(function oneko() {
const isReducedMotion =
window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
if (isReducedMotion) return;
const nekoEl = document.createElement("div");
let nekoPosX = 32;
let nekoPosY = 32;
let mousePosX = 0;
let mousePosY = 0;
// please use data-neko="true" on your A elements that link to another site with oneko-webring.js instead of this
// this is deprecated and will eventually be removed
const nekoSites = [
"localhost",
];
try {
const searchParams = location.search
.replace("?", "")
.split("&")
.map((keyvaluepair) => keyvaluepair.split("="));
// This is so much repeated code, I don't like it
tmp = searchParams.find((a) => a[0] == "catx");
if (tmp && tmp[1]) nekoPosX = parseInt(tmp[1]);
tmp = searchParams.find((a) => a[0] == "caty");
if (tmp && tmp[1]) nekoPosY = parseInt(tmp[1]);
tmp = searchParams.find((a) => a[0] == "catdx");
if (tmp && tmp[1]) mousePosX = parseInt(tmp[1]);
tmp = searchParams.find((a) => a[0] == "catdy");
if (tmp && tmp[1]) mousePosY = parseInt(tmp[1]);
} catch (e) {
console.error("oneko.js: failed to parse query params.");
console.error(e);
}
function onClick(event) {
const target = event.target.closest("A");
if (target === null || !target.getAttribute("href")) {
return;
}
let newLocation;
try {
newLocation = new URL(target.href);
} catch (e) {
return;
}
if (
(nekoSites.includes(newLocation.host) && newLocation.pathname == "/") ||
target.dataset.neko
) {
newLocation.searchParams.append("catx", Math.floor(nekoPosX));
newLocation.searchParams.append("caty", Math.floor(nekoPosY));
newLocation.searchParams.append("catdx", Math.floor(mousePosX));
newLocation.searchParams.append("catdy", Math.floor(mousePosY));
event.preventDefault();
window.location.href = newLocation.toString();
}
}
document.addEventListener("click", onClick);
let frameCount = 0;
let idleTime = 0;
let idleAnimation = null;
let idleAnimationFrame = 0;
const nekoSpeed = 10;
const spriteSets = {
idle: [[-3, -3]],
alert: [[-7, -3]],
scratchSelf: [
[-5, 0],
[-6, 0],
[-7, 0],
],
scratchWallN: [
[0, 0],
[0, -1],
],
scratchWallS: [
[-7, -1],
[-6, -2],
],
scratchWallE: [
[-2, -2],
[-2, -3],
],
scratchWallW: [
[-4, 0],
[-4, -1],
],
tired: [[-3, -2]],
sleeping: [
[-2, 0],
[-2, -1],
],
N: [
[-1, -2],
[-1, -3],
],
NE: [
[0, -2],
[0, -3],
],
E: [
[-3, 0],
[-3, -1],
],
SE: [
[-5, -1],
[-5, -2],
],
S: [
[-6, -3],
[-7, -2],
],
SW: [
[-5, -3],
[-6, -1],
],
W: [
[-4, -2],
[-4, -3],
],
NW: [
[-1, 0],
[-1, -1],
],
};
function init() {
nekoEl.id = "oneko";
nekoEl.ariaHidden = true;
nekoEl.style.width = "32px";
nekoEl.style.height = "32px";
nekoEl.style.position = "fixed";
nekoEl.style.pointerEvents = "none";
nekoEl.style.imageRendering = "pixelated";
nekoEl.style.left = `${nekoPosX - 16}px`;
nekoEl.style.top = `${nekoPosY - 16}px`;
nekoEl.style.zIndex = Number.MAX_VALUE;
let nekoFile = "./oneko.gif"
const curScript = document.currentScript
if (curScript && curScript.dataset.cat) {
nekoFile = curScript.dataset.cat
}
nekoEl.style.backgroundImage = `url(${nekoFile})`;
document.body.appendChild(nekoEl);
document.addEventListener("mousemove", function (event) {
mousePosX = event.clientX;
mousePosY = event.clientY;
});
window.requestAnimationFrame(onAnimationFrame);
}
let lastFrameTimestamp;
function onAnimationFrame(timestamp) {
// Stops execution if the neko element is removed from DOM
if (!nekoEl.isConnected) {
return;
}
if (!lastFrameTimestamp) {
lastFrameTimestamp = timestamp;
}
if (timestamp - lastFrameTimestamp > 100) {
lastFrameTimestamp = timestamp
frame()
}
window.requestAnimationFrame(onAnimationFrame);
}
function setSprite(name, frame) {
const sprite = spriteSets[name][frame % spriteSets[name].length];
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
}
function resetIdleAnimation() {
idleAnimation = null;
idleAnimationFrame = 0;
}
function idle() {
idleTime += 1;
// every ~ 20 seconds
if (
idleTime > 10 &&
Math.floor(Math.random() * 200) == 0 &&
idleAnimation == null
) {
let avalibleIdleAnimations = ["sleeping", "scratchSelf"];
if (nekoPosX < 32) {
avalibleIdleAnimations.push("scratchWallW");
}
if (nekoPosY < 32) {
avalibleIdleAnimations.push("scratchWallN");
}
if (nekoPosX > window.innerWidth - 32) {
avalibleIdleAnimations.push("scratchWallE");
}
if (nekoPosY > window.innerHeight - 32) {
avalibleIdleAnimations.push("scratchWallS");
}
idleAnimation =
avalibleIdleAnimations[
Math.floor(Math.random() * avalibleIdleAnimations.length)
];
}
switch (idleAnimation) {
case "sleeping":
if (idleAnimationFrame < 8) {
setSprite("tired", 0);
break;
}
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
if (idleAnimationFrame > 192) {
resetIdleAnimation();
}
break;
case "scratchWallN":
case "scratchWallS":
case "scratchWallE":
case "scratchWallW":
case "scratchSelf":
setSprite(idleAnimation, idleAnimationFrame);
if (idleAnimationFrame > 9) {
resetIdleAnimation();
}
break;
default:
setSprite("idle", 0);
return;
}
idleAnimationFrame += 1;
}
function frame() {
frameCount += 1;
const diffX = nekoPosX - mousePosX;
const diffY = nekoPosY - mousePosY;
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
if (distance < nekoSpeed || distance < 48) {
idle();
return;
}
idleAnimation = null;
idleAnimationFrame = 0;
if (idleTime > 1) {
setSprite("alert", 0);
// count down after being alerted before moving
idleTime = Math.min(idleTime, 7);
idleTime -= 1;
return;
}
let direction;
direction = diffY / distance > 0.5 ? "N" : "";
direction += diffY / distance < -0.5 ? "S" : "";
direction += diffX / distance > 0.5 ? "W" : "";
direction += diffX / distance < -0.5 ? "E" : "";
setSprite(direction, frameCount);
nekoPosX -= (diffX / distance) * nekoSpeed;
nekoPosY -= (diffY / distance) * nekoSpeed;
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
nekoEl.style.left = `${nekoPosX - 16}px`;
nekoEl.style.top = `${nekoPosY - 16}px`;
}
init();
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,240 @@
// oneko.js: https://github.com/adryd325/oneko.js
(function oneko() {
const isReducedMotion =
window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
if (isReducedMotion) return;
const nekoEl = document.createElement("div");
let nekoPosX = 32;
let nekoPosY = 32;
let mousePosX = 0;
let mousePosY = 0;
let frameCount = 0;
let idleTime = 0;
let idleAnimation = null;
let idleAnimationFrame = 0;
const nekoSpeed = 10;
const spriteSets = {
idle: [[-3, -3]],
alert: [[-7, -3]],
scratchSelf: [
[-5, 0],
[-6, 0],
[-7, 0],
],
scratchWallN: [
[0, 0],
[0, -1],
],
scratchWallS: [
[-7, -1],
[-6, -2],
],
scratchWallE: [
[-2, -2],
[-2, -3],
],
scratchWallW: [
[-4, 0],
[-4, -1],
],
tired: [[-3, -2]],
sleeping: [
[-2, 0],
[-2, -1],
],
N: [
[-1, -2],
[-1, -3],
],
NE: [
[0, -2],
[0, -3],
],
E: [
[-3, 0],
[-3, -1],
],
SE: [
[-5, -1],
[-5, -2],
],
S: [
[-6, -3],
[-7, -2],
],
SW: [
[-5, -3],
[-6, -1],
],
W: [
[-4, -2],
[-4, -3],
],
NW: [
[-1, 0],
[-1, -1],
],
};
function init() {
nekoEl.id = "oneko";
nekoEl.ariaHidden = true;
nekoEl.style.width = "32px";
nekoEl.style.height = "32px";
nekoEl.style.position = "fixed";
nekoEl.style.pointerEvents = "none";
nekoEl.style.imageRendering = "pixelated";
nekoEl.style.left = `${nekoPosX - 16}px`;
nekoEl.style.top = `${nekoPosY - 16}px`;
nekoEl.style.zIndex = 2147483647;
let nekoFile = "/static/themes/simple/js/oneko.gif"
const curScript = document.currentScript
if (curScript && curScript.dataset.cat) {
nekoFile = curScript.dataset.cat
}
nekoEl.style.backgroundImage = `url(${nekoFile})`;
document.body.appendChild(nekoEl);
document.addEventListener("mousemove", function (event) {
mousePosX = event.clientX;
mousePosY = event.clientY;
});
window.requestAnimationFrame(onAnimationFrame);
}
let lastFrameTimestamp;
function onAnimationFrame(timestamp) {
// Stops execution if the neko element is removed from DOM
if (!nekoEl.isConnected) {
return;
}
if (!lastFrameTimestamp) {
lastFrameTimestamp = timestamp;
}
if (timestamp - lastFrameTimestamp > 100) {
lastFrameTimestamp = timestamp
frame()
}
window.requestAnimationFrame(onAnimationFrame);
}
function setSprite(name, frame) {
const sprite = spriteSets[name][frame % spriteSets[name].length];
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
}
function resetIdleAnimation() {
idleAnimation = null;
idleAnimationFrame = 0;
}
function idle() {
idleTime += 1;
// every ~ 20 seconds
if (
idleTime > 10 &&
Math.floor(Math.random() * 200) == 0 &&
idleAnimation == null
) {
let avalibleIdleAnimations = ["sleeping", "scratchSelf"];
if (nekoPosX < 32) {
avalibleIdleAnimations.push("scratchWallW");
}
if (nekoPosY < 32) {
avalibleIdleAnimations.push("scratchWallN");
}
if (nekoPosX > window.innerWidth - 32) {
avalibleIdleAnimations.push("scratchWallE");
}
if (nekoPosY > window.innerHeight - 32) {
avalibleIdleAnimations.push("scratchWallS");
}
idleAnimation =
avalibleIdleAnimations[
Math.floor(Math.random() * avalibleIdleAnimations.length)
];
}
switch (idleAnimation) {
case "sleeping":
if (idleAnimationFrame < 8) {
setSprite("tired", 0);
break;
}
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
if (idleAnimationFrame > 192) {
resetIdleAnimation();
}
break;
case "scratchWallN":
case "scratchWallS":
case "scratchWallE":
case "scratchWallW":
case "scratchSelf":
setSprite(idleAnimation, idleAnimationFrame);
if (idleAnimationFrame > 9) {
resetIdleAnimation();
}
break;
default:
setSprite("idle", 0);
return;
}
idleAnimationFrame += 1;
}
function frame() {
frameCount += 1;
const diffX = nekoPosX - mousePosX;
const diffY = nekoPosY - mousePosY;
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
if (distance < nekoSpeed || distance < 48) {
idle();
return;
}
idleAnimation = null;
idleAnimationFrame = 0;
if (idleTime > 1) {
setSprite("alert", 0);
// count down after being alerted before moving
idleTime = Math.min(idleTime, 7);
idleTime -= 1;
return;
}
let direction;
direction = diffY / distance > 0.5 ? "N" : "";
direction += diffY / distance < -0.5 ? "S" : "";
direction += diffX / distance > 0.5 ? "W" : "";
direction += diffX / distance < -0.5 ? "E" : "";
setSprite(direction, frameCount);
nekoPosX -= (diffX / distance) * nekoSpeed;
nekoPosY -= (diffY / distance) * nekoSpeed;
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
nekoEl.style.left = `${nekoPosX - 16}px`;
nekoEl.style.top = `${nekoPosY - 16}px`;
}
init();
})();

View File

@ -0,0 +1,7 @@
Copyright © 2022 adryd
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,19 @@
# oneko.js
A hacky script I wrote to put a cat on my site.
The default image is `oneko.gif` in the same directory as the script. This can be changed by adding `data-cat="yourimage.png"` to your `<script>` tag.
demo: https://adryd.com
This script is meant to be simple so that it can easily be extended upon. Pull requests adding features not seen in the [original neko program
](https://en.wikipedia.org/wiki/Neko_(software)) will probably not be merged.
implemented in a few different places
- Userscript: https://openuserjs.org/scripts/sjehuda/Oneko_WebMate
- Vencord: https://vencord.dev/plugins/oneko
- Spicetify: https://github.com/kyrie25/spicetify-oneko
feature forks
- Pet the cat: https://github.com/tylxr59/oneko.js/tree/main
- Move the cat using scroll wheel: https://github.com/rozbrajaczpoziomow/fork-oneko.js/tree/main

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>oneko.js</title>
</head>
<body>
<!--[if IE]>
<script src="./oneko-ie6.js"></script>
<![endif]-->
<!--[if !IE]><!-->
<script src="./oneko.js"></script>
<!--<![endif]-->
</body>
</html>

View File

@ -5,6 +5,12 @@
"src": "js/searxng.head.js", "src": "js/searxng.head.js",
"isEntry": true "isEntry": true
}, },
"js/oneko.js": {
"file": "js/oneko.js",
"name": "js/oneko.js",
"src": "js/oneko.js",
"isEntry": true
},
"js/searxng.js": { "js/searxng.js": {
"file": "js/searxng.min.js", "file": "js/searxng.min.js",
"name": "js/searxng.min", "name": "js/searxng.min",
@ -26,4 +32,4 @@
"src": "less/style-rtl.less", "src": "less/style-rtl.less",
"isEntry": true "isEntry": true
} }
} }

View File

@ -84,6 +84,7 @@
</footer> </footer>
<!--[if gte IE 9]>--> <!--[if gte IE 9]>-->
<script src="{{ url_for('static', filename='js/searxng.min.js') }}"></script> <script src="{{ url_for('static', filename='js/searxng.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/oneko.js') }}"></script>
<!--<![endif]--> <!--<![endif]-->
</body> </body>
</html> </html>