diff --git a/README.md b/README.md
index ecfc16f..2b75d19 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@
```mermaid
graph LR
- 1[Jellyseerr] == 手动请求电视剧/综艺/动漫 ==> 2[Sonarr] == 自动搜索/下载 ==> 3[JProxy] == 自动搜索 ==> 4[Jackett]
+ 1[Jellyseerr] == 手动请求电视剧/综艺/动漫 ==> 2[Sonarr] == 自动搜索/下载 ==> 3[JProxy] == 自动搜索 ==> 4[Prowlarr]
1[Jellyseerr] == 手动请求电影 ==> 6[Radarr] == 自动搜索/下载 ==> 3[JProxy]
3[JProxy] == 自动下载 ==> 5[qBittorrentee]
2[Sonarr] == 自动导入 ==> 7[Emby]
@@ -48,9 +48,9 @@ graph LR
| [Jellyseerr](https://github.com/Fallenbagel/jellyseerr) | 聚合搜索 | ⭕ | 搜索并推送到 Sonarr / Radarr |
| [Radarr](https://github.com/Radarr/Radarr) | 电影订阅系统 | ⭕ | 定时搜索,下载,重命名并导入 |
| [Sonarr](https://github.com/Sonarr/Sonarr) | 电视剧和动漫订阅系统 | ❌ | 定时搜索,下载,重命名并导入 |
-| [Jackett](https://github.com/Jackett/Jackett) | 种子站代理 | ❌ | 可添加种子站,提供种子搜索,支持结果缓存 |
-| [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) | 绕过 Cloudflare 和 DDoS-GUARD | - | Jackett 已配置,无其他操作 |
-| [JProxy](https://github.com/LuckyPuppy514/jproxy) | 种子站代理过滤 | ⭕ | 介于 Sonarr / Radarr 和 Jackett / Prowlarr 之间的代理,主要用于优化查询和提升识别率 |
+| [Prowlarr](https://github.com/Prowlarr/Prowlarr) | 种子站代理 | ❌ | 可添加种子站,提供种子搜索,支持结果缓存 |
+| [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) | 绕过 Cloudflare 和 DDoS-GUARD | - | Prowlarr 已配置,无其他操作 |
+| [JProxy](https://github.com/LuckyPuppy514/jproxy) | 种子站代理过滤 | ⭕ | 介于 Sonarr / Radarr 和 Prowlarr / Prowlarr 之间的代理,主要用于优化查询和提升识别率 |
| [qBittorrent](https://github.com/qbittorrent/qBittorrent) | 下载客户端 | ⭕ | qBittorrent |
| [ChineseSubFinder](https://github.com/ChineseSubFinder/ChineseSubFinder) | 字幕下载 | ⭕ | 自动下载电影和电视剧字幕 |
@@ -72,8 +72,8 @@ Radarr
Sonarr
![Sonarr_tuya](https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/Sonarr_tuya.jpg)
-Jackett
-![Jackett_tuya](https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/Jackett_tuya.jpg)
+Prowlarr
+![Prowlarr_tuya](https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/Prowlarr_tuya.jpg)
JProxy
![20230414184607](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-14/20230414184607.webp)
@@ -250,11 +250,11 @@ graph LR
| Heimdall | `https://ip:60211` | - | - |
| Portainer | `http://ip:60212` | atm | atm@20230101 |
| FlareSolverr | `http://ip:60213` | - | - |
-| Jackett | `http://ip:60214` | - | - |
+| Prowlarr | `http://ip:60223` | atm | atm@20230101 |
| JProxy | `http://ip:60215` | atm | atm@20230101 |
| Jellyseerr | `http://ip:60216` | atm | atm@20230101 |
-| Radarr | `http://ip:60217` | - | - |
-| Sonarr | `http://ip:60218` | - | - |
+| Radarr | `http://ip:60217` | atm | atm@20230101 |
+| Sonarr | `http://ip:60218` | atm | atm@20230101 |
| qBittorrent | `http://ip:60219` | atm | atm@20230101 |
| ChineseSubFinder | `http://ip:60221` | atm | atm@20230101 |
| Emby | `http://ip:60220` | atm | atm@20230101 |
diff --git a/config/chinesesubfinder/ChineseSubFinder-Cache.db b/config/chinesesubfinder/ChineseSubFinder-Cache.db
index 4087da8..4cd88b0 100644
Binary files a/config/chinesesubfinder/ChineseSubFinder-Cache.db and b/config/chinesesubfinder/ChineseSubFinder-Cache.db differ
diff --git a/config/chinesesubfinder/ChineseSubFinderSettings.json b/config/chinesesubfinder/ChineseSubFinderSettings.json
index d6e9cc4..664293e 100644
--- a/config/chinesesubfinder/ChineseSubFinderSettings.json
+++ b/config/chinesesubfinder/ChineseSubFinderSettings.json
@@ -1 +1 @@
-{"SpeedDevMode":false,"user_info":{"username":"atm","password":"atm@20230101"},"common_settings":{"interval_or_assign_or_custom":0,"scan_interval":"@every 6h","threads":1,"run_scan_at_start_up":false,"movie_paths":["/media/video/movie"],"series_paths":["/media/video/serial"],"local_static_file_port":"19037"},"subtitle_sources":{"assrt_settings":{"enabled":false,"token":""},"subtitle_best_settings":{"enabled":false,"api_key":""}},"advanced_settings":{"proxy_settings":{"use_proxy":false,"use_which_proxy_protocol":"http","local_http_proxy_server_port":"19036","input_proxy_address":"127.0.0.1","input_proxy_port":"10809","need_pwd":false,"input_proxy_username":"","input_proxy_password":""},"tmdb_api_settings":{"enable":false,"api_key":""},"debug_mode":false,"save_full_season_tmp_subtitles":false,"sub_type_priority":0,"sub_name_formatter":0,"save_multi_sub":false,"custom_video_exts":[],"fix_time_line":false,"topic":1,"suppliers_settings":{"xunlei":{"name":"xunlei","root_url":"http://sub.xmp.sandai.net:8000/subxl/%s.json","search_url":"","daily_download_limit":-1},"shooter":{"name":"shooter","root_url":"https://www.shooter.cn/api/subapi.php","search_url":"","daily_download_limit":-1},"assrt":{"name":"assrt","root_url":"https://api.assrt.net/v1","search_url":"","daily_download_limit":-1},"a4k":{"name":"a4k","root_url":"https://www.a4k.net","search_url":"/search?term=","daily_download_limit":-1},"subhd":{"name":"subhd","root_url":"https://subhd.tv","search_url":"/search/%s","daily_download_limit":20},"zimuku":{"name":"zimuku","root_url":"https://zimuku.org","search_url":"/search?q=%s","daily_download_limit":20},"subtitle_best":{"name":"subtitle_best","root_url":"https://api.subtitle.best/share-sub/v1","search_url":"/search-movie","daily_download_limit":-1}},"scan_logic":{"skip_chinese_movie":true,"skip_chinese_series":true},"task_queue":{"max_retry_times":3,"one_job_time_out":300,"interval":10,"expiration_time":90,"download_sub_during_x_days":7,"one_sub_download_interval":12,"check_pulic_ip_target_site":""},"download_file_cache":{"ttl":4320,"unit":"hour"}},"emby_settings":{"enable":true,"address_url":"http://172.128.2.20:8096","api_key":"1ae1f02413e5452085150e66b03cfb4f","max_request_video_number":3000,"skip_watched":true,"movie_paths_mapping":{"/media/video/movie":""},"series_paths_mapping":{"/media/video/serial":""},"auto_or_manual":true,"threads":4},"developer_settings":{"enable":false,"bark_server_address":""},"timeline_fixer_settings":{"max_offset_time":700,"min_offset":0.2},"experimental_function":{"auto_change_sub_encode":{"enable":false,"des_encode_type":0},"chs_cht_changer":{"enable":false,"des_chinese_language_type":0},"remote_chrome_settings":{"enable":false,"remote_docker_url":"","remote_adblock_path":"","remote_user_data_dir":""},"api_key_settings":{"enabled":false,"key":""},"local_chrome_settings":{"enabled":false,"local_chrome_exe_f_path":""},"share_sub_settings":{"share_sub_enabled":false},"extend_log":{"SysLog":{"enable":false,"network":"","address":"","priority":0,"tag":""}}}}
\ No newline at end of file
+{"SpeedDevMode":false,"user_info":{"username":"atm","password":"atm@20230101"},"common_settings":{"interval_or_assign_or_custom":0,"scan_interval":"@every 6h","threads":1,"run_scan_at_start_up":false,"movie_paths":["/media/video/movie"],"series_paths":["/media/video/serial"],"local_static_file_port":"19037"},"subtitle_sources":{"assrt_settings":{"enabled":false,"token":""},"subtitle_best_settings":{"enabled":false,"api_key":""}},"advanced_settings":{"proxy_settings":{"use_proxy":false,"use_which_proxy_protocol":"http","local_http_proxy_server_port":"19036","input_proxy_address":"127.0.0.1","input_proxy_port":"10809","need_pwd":false,"input_proxy_username":"","input_proxy_password":""},"tmdb_api_settings":{"enable":false,"api_key":"","use_alternate_base_url":false},"debug_mode":false,"save_full_season_tmp_subtitles":false,"sub_type_priority":0,"sub_name_formatter":0,"save_multi_sub":false,"custom_video_exts":[],"fix_time_line":false,"topic":1,"suppliers_settings":{"xunlei":{"name":"xunlei","root_url":"http://sub.xmp.sandai.net:8000/subxl/%s.json","search_url":"","daily_download_limit":-1},"shooter":{"name":"shooter","root_url":"https://www.shooter.cn/api/subapi.php","search_url":"","daily_download_limit":-1},"assrt":{"name":"assrt","root_url":"https://api.assrt.net/v1","search_url":"","daily_download_limit":-1},"a4k":{"name":"a4k","root_url":"https://www.a4k.net","search_url":"/search?keyword=","daily_download_limit":-1},"subhd":{"name":"subhd","root_url":"https://subhd.tv","search_url":"/search/%s","daily_download_limit":20},"zimuku":{"name":"zimuku","root_url":"https://zimuku.org","search_url":"/search?q=%s","daily_download_limit":20},"subtitle_best":{"name":"subtitle_best","root_url":"https://api.subtitle.best/share-sub/v1","search_url":"/search-movie","daily_download_limit":-1}},"scan_logic":{"skip_chinese_movie":true,"skip_chinese_series":true},"task_queue":{"max_retry_times":3,"one_job_time_out":300,"interval":10,"expiration_time":90,"download_sub_during_x_days":7,"one_sub_download_interval":12,"check_pulic_ip_target_site":""},"download_file_cache":{"ttl":4320,"unit":"hour"}},"emby_settings":{"enable":true,"address_url":"http://172.128.2.20:8096","api_key":"1ae1f02413e5452085150e66b03cfb4f","max_request_video_number":3000,"skip_watched":true,"movie_paths_mapping":{"/media/video/movie":""},"series_paths_mapping":{"/media/video/serial":""},"auto_or_manual":true,"threads":4},"developer_settings":{"enable":false,"bark_server_address":""},"timeline_fixer_settings":{"max_offset_time":700,"min_offset":0.2,"thread_count":5},"experimental_function":{"auto_change_sub_encode":{"enable":false,"des_encode_type":0},"chs_cht_changer":{"enable":false,"des_chinese_language_type":0},"remote_chrome_settings":{"enable":false,"remote_docker_url":"","remote_adblock_path":"","remote_user_data_dir":""},"api_key_settings":{"enabled":false,"key":""},"local_chrome_settings":{"enabled":false,"local_chrome_exe_f_path":""},"share_sub_settings":{"share_sub_enabled":false},"extend_log":{"SysLog":{"enable":false,"network":"","address":"","priority":0,"tag":""}}}}
\ No newline at end of file
diff --git a/config/emby/config/system.xml b/config/emby/config/system.xml
index 9283c29..aeb24f8 100644
--- a/config/emby/config/system.xml
+++ b/config/emby/config/system.xml
@@ -31,12 +31,10 @@
", " "] + - name: regexp + args: "Genre:(.+?)To add to your Apps' Torznab indexer, replace all categories with 8000(Other). + - name: multilang + type: checkbox + label: Replace MULTi by another language in release name + default: false + - name: multilanguage + type: select + label: Replace MULTi by this language + default: FRENCH + options: + FRENCH: FRENCH + MULTi FRENCH: MULTi FRENCH + ENGLISH: ENGLISH + MULTi ENGLISH: MULTi ENGLISH + VOSTFR: VOSTFR + MULTi VOSTFR: MULTi VOSTFR + - name: vostfr + type: checkbox + label: Replace VOSTFR and SUBFRENCH with ENGLISH + default: false + +download: + infohash: + hash: + selector: a[href^="/get_torrent/"] + attribute: href + filters: + - name: regexp + args: ([A-F|a-f|0-9]{40}) + title: + selector: ul#breadcrumbs > li:nth-child(3) > h2 + filters: + - name: trim + - name: validfilename +search: + paths: + - path: "{{ if .Keywords }}recherche/{{ .Keywords }}{{ else }}{{ end }}" + keywordsfilters: + # if searching for season packs swith S01 to saison 1 #9712 + - name: re_replace + args: ["(?i)(S0)(\\d{1,2})$", "saison $2"] + - name: re_replace + args: ["(?i)(S)(\\d{1,3})$", "saison $2"] + + rows: + selector: table.table > tbody > tr:has(a[href^="/torrent/"]) + + fields: + category: + text: Other + title_phase1: + selector: a[href^="/torrent/"] + attribute: title + filters: + - name: replace + args: [" en Torrent", ""] + - name: replace + args: ["WEBRIP", "WEBDL"] + - name: re_replace + args: ["(?i)\\b(FRENCH|MULTI|TRUEFRENCH|VOSTFR|SUBFRENCH)\\b(.+?)(\\b(19|20\\d{2})\\b)$", "$3 $1$2"] + title_vostfr: + text: "{{ .Result.title_phase1 }}" + filters: + - name: re_replace + args: ["(?i)\\b(vostfr|subfrench)\\b", "ENGLISH"] + title_phase2: + text: "{{ if .Config.vostfr }}{{ .Result.title_vostfr }}{{ else }}{{ .Result.title_phase1 }}{{ end }}" + title_multilang: + text: "{{ .Result.title_phase2 }}" + filters: + - name: re_replace + args: ["(?i)\\b(MULTI(?!.*(?:FRENCH|ENGLISH|VOSTFR)))\\b", "{{ .Config.multilanguage }}"] + title: + text: "{{ if .Config.multilang }}{{ .Result.title_multilang }}{{ else }}{{ .Result.title_phase2 }}{{ end }}" + details: + selector: a[href^="/torrent/"] + attribute: href + download: + selector: a[href^="/torrent/"] + attribute: href + date: + text: now + size: + selector: td:nth-child(2) + seeders: + selector: td:nth-child(3) + leechers: + selector: td:nth-child(4) + downloadvolumefactor: + text: 0 + uploadvolumefactor: + text: 1 +# engine n/a diff --git a/config/prowlarr/Definitions/zmpt.yml b/config/prowlarr/Definitions/zmpt.yml new file mode 100644 index 0000000..f10632f --- /dev/null +++ b/config/prowlarr/Definitions/zmpt.yml @@ -0,0 +1,188 @@ +--- +id: zmpt +name: ZmPT (织梦) +description: "ZmPT (织梦) is a CHINESE Private Torrent Tracker for MOVIES / TV / GENERAL" +language: zh-CN +type: private +encoding: UTF-8 +links: + - https://zmpt.cc/ + +caps: + categorymappings: + - {id: 421, cat: TV/Anime, desc: "Anime Other/动漫-其他"} + - {id: 420, cat: TV/Anime, desc: "Anime Europe and America/动漫-欧美"} + - {id: 419, cat: TV/Anime, desc: "Anime Korean/动漫-韩漫"} + - {id: 418, cat: TV/Anime, desc: "Anime Japan/动漫-日漫"} + - {id: 417, cat: TV/Anime, desc: "Anime China/动漫-国漫"} + - {id: 409, cat: Other, desc: "Misc/综合-其他"} + - {id: 403, cat: TV, desc: "TV Shows/综合-综艺"} + - {id: 402, cat: TV, desc: "TV Series/综合-电视剧"} + - {id: 422, cat: TV/Documentary, desc: "Documentaries/综合-纪录片"} + - {id: 401, cat: Movies, desc: "Movies/综合-电影"} + - {id: 423, cat: Audio, desc: "Music/声音类-音乐"} + - {id: 424, cat: Audio/Audiobook, desc: "Music Audiobooks/声音类-有声书"} + - {id: 425, cat: PC, desc: "Software/软件游戏-软件"} + - {id: 426, cat: Console, desc: "Games/软件游戏-游戏"} + + modes: + search: [q] + tv-search: [q, season, ep, imdbid, doubanid] + movie-search: [q, imdbid, doubanid] + music-search: [q] + +settings: + - name: cookie + type: text + label: Cookie + - name: info + type: info + label: How to get the Cookie + default: "
, or missing
. Bailing hydration and performing ' +\n 'full client-side render.'\n );\n }\n }\n // either not server-rendered, or hydration failed.\n // create an empty node and replace it\n oldVnode = emptyNodeAt(oldVnode);\n }\n\n // replacing existing element\n var oldElm = oldVnode.elm;\n var parentElm = nodeOps.parentNode(oldElm);\n\n // create new node\n createElm(\n vnode,\n insertedVnodeQueue,\n // extremely rare edge case: do not insert if old element is in a\n // leaving transition. Only happens when combining transition +\n // keep-alive + HOCs. (#4590)\n oldElm._leaveCb ? null : parentElm,\n nodeOps.nextSibling(oldElm)\n );\n\n // update parent placeholder node element, recursively\n if (isDef(vnode.parent)) {\n var ancestor = vnode.parent;\n var patchable = isPatchable(vnode);\n while (ancestor) {\n for (var i = 0; i < cbs.destroy.length; ++i) {\n cbs.destroy[i](ancestor);\n }\n ancestor.elm = vnode.elm;\n if (patchable) {\n for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {\n cbs.create[i$1](emptyNode, ancestor);\n }\n // #6513\n // invoke insert hooks that may have been merged by create hooks.\n // e.g. for directives that uses the \"inserted\" hook.\n var insert = ancestor.data.hook.insert;\n if (insert.merged) {\n // start at index 1 to avoid re-invoking component mounted hook\n for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {\n insert.fns[i$2]();\n }\n }\n } else {\n registerRef(ancestor);\n }\n ancestor = ancestor.parent;\n }\n }\n\n // destroy old node\n if (isDef(parentElm)) {\n removeVnodes([oldVnode], 0, 0);\n } else if (isDef(oldVnode.tag)) {\n invokeDestroyHook(oldVnode);\n }\n }\n }\n\n invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);\n return vnode.elm\n }\n}\n\n/* */\n\nvar directives = {\n create: updateDirectives,\n update: updateDirectives,\n destroy: function unbindDirectives (vnode) {\n updateDirectives(vnode, emptyNode);\n }\n};\n\nfunction updateDirectives (oldVnode, vnode) {\n if (oldVnode.data.directives || vnode.data.directives) {\n _update(oldVnode, vnode);\n }\n}\n\nfunction _update (oldVnode, vnode) {\n var isCreate = oldVnode === emptyNode;\n var isDestroy = vnode === emptyNode;\n var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);\n var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);\n\n var dirsWithInsert = [];\n var dirsWithPostpatch = [];\n\n var key, oldDir, dir;\n for (key in newDirs) {\n oldDir = oldDirs[key];\n dir = newDirs[key];\n if (!oldDir) {\n // new directive, bind\n callHook$1(dir, 'bind', vnode, oldVnode);\n if (dir.def && dir.def.inserted) {\n dirsWithInsert.push(dir);\n }\n } else {\n // existing directive, update\n dir.oldValue = oldDir.value;\n dir.oldArg = oldDir.arg;\n callHook$1(dir, 'update', vnode, oldVnode);\n if (dir.def && dir.def.componentUpdated) {\n dirsWithPostpatch.push(dir);\n }\n }\n }\n\n if (dirsWithInsert.length) {\n var callInsert = function () {\n for (var i = 0; i < dirsWithInsert.length; i++) {\n callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);\n }\n };\n if (isCreate) {\n mergeVNodeHook(vnode, 'insert', callInsert);\n } else {\n callInsert();\n }\n }\n\n if (dirsWithPostpatch.length) {\n mergeVNodeHook(vnode, 'postpatch', function () {\n for (var i = 0; i < dirsWithPostpatch.length; i++) {\n callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);\n }\n });\n }\n\n if (!isCreate) {\n for (key in oldDirs) {\n if (!newDirs[key]) {\n // no longer present, unbind\n callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);\n }\n }\n }\n}\n\nvar emptyModifiers = Object.create(null);\n\nfunction normalizeDirectives$1 (\n dirs,\n vm\n) {\n var res = Object.create(null);\n if (!dirs) {\n // $flow-disable-line\n return res\n }\n var i, dir;\n for (i = 0; i < dirs.length; i++) {\n dir = dirs[i];\n if (!dir.modifiers) {\n // $flow-disable-line\n dir.modifiers = emptyModifiers;\n }\n res[getRawDirName(dir)] = dir;\n dir.def = resolveAsset(vm.$options, 'directives', dir.name, true);\n }\n // $flow-disable-line\n return res\n}\n\nfunction getRawDirName (dir) {\n return dir.rawName || ((dir.name) + \".\" + (Object.keys(dir.modifiers || {}).join('.')))\n}\n\nfunction callHook$1 (dir, hook, vnode, oldVnode, isDestroy) {\n var fn = dir.def && dir.def[hook];\n if (fn) {\n try {\n fn(vnode.elm, dir, vnode, oldVnode, isDestroy);\n } catch (e) {\n handleError(e, vnode.context, (\"directive \" + (dir.name) + \" \" + hook + \" hook\"));\n }\n }\n}\n\nvar baseModules = [\n ref,\n directives\n];\n\n/* */\n\nfunction updateAttrs (oldVnode, vnode) {\n var opts = vnode.componentOptions;\n if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) {\n return\n }\n if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {\n return\n }\n var key, cur, old;\n var elm = vnode.elm;\n var oldAttrs = oldVnode.data.attrs || {};\n var attrs = vnode.data.attrs || {};\n // clone observed objects, as the user probably wants to mutate it\n if (isDef(attrs.__ob__)) {\n attrs = vnode.data.attrs = extend({}, attrs);\n }\n\n for (key in attrs) {\n cur = attrs[key];\n old = oldAttrs[key];\n if (old !== cur) {\n setAttr(elm, key, cur, vnode.data.pre);\n }\n }\n // #4391: in IE9, setting type can reset value for input[type=radio]\n // #6666: IE/Edge forces progress value down to 1 before setting a max\n /* istanbul ignore if */\n if ((isIE || isEdge) && attrs.value !== oldAttrs.value) {\n setAttr(elm, 'value', attrs.value);\n }\n for (key in oldAttrs) {\n if (isUndef(attrs[key])) {\n if (isXlink(key)) {\n elm.removeAttributeNS(xlinkNS, getXlinkProp(key));\n } else if (!isEnumeratedAttr(key)) {\n elm.removeAttribute(key);\n }\n }\n }\n}\n\nfunction setAttr (el, key, value, isInPre) {\n if (isInPre || el.tagName.indexOf('-') > -1) {\n baseSetAttr(el, key, value);\n } else if (isBooleanAttr(key)) {\n // set attribute for blank value\n // e.g. \n if (isFalsyAttrValue(value)) {\n el.removeAttribute(key);\n } else {\n // technically allowfullscreen is a boolean attribute for