update 2026-09-26 11:07:27

This commit is contained in:
action committed 2026-09-26 11:07:27 +08:00
1 parent 7b88b273d0
commit 97ae9f68da
4898 files changed
-1712274

No files matched your search

@@ -1,19 +0,0 @@
---
name: "\U0001F41B 错误报告 | Bug Report"
about: 请详细描述您使用过程中遇到的问题。| Please describe in detail the problems you encountered in
the process of using.
title: "\"\U0001F41B 一些问题。。。 | [Bug] Some problem...\""
labels: bug
assignees: ''
---
<!-- 请在您提交 bug 之前,回答以下这些问题。 | Please answer these questions before you submit a bug. -->
#### 您使用的版本? | Your usage version?
#### 您使用的场景? | Your usage scenarios?
#### 您遇到了什么问题? | What are your problems?
#### 您期望的结果是怎样的? | What is your expected outcome?
@@ -1,22 +0,0 @@
---
name: "\U0001F64B 问题交流 | Question Report"
about: 在文档或讨论中没有回答的使用问题 | Usage question that isn't answered in docs or discussion
title: "\"\U0001F64B 问题交流。。。 | [Question] Some question...\""
labels: help wanted
assignees: ''
---
- 搜索打开和关闭的 [GitHub 问题](https://github.com/eryajf/go-ldap-admin/issues)
请在提交问题之前回答这些问题,谢谢。 | Please answer these questions before submitting them. Thank you.
### 你使用了哪个版本? | Which version did you use?
### 预期行为 | Expected behavior
### 实际行为 | Actual behavior
### 原因分析(如果可以) | Cause analysis (if possible)
### 问题重现步骤 | Steps to reproduce the problem
@@ -1,14 +0,0 @@
---
name: "\U0001F680 功能请求 | Feature Request"
about: 请详细描述您期望的功能。 | Please describe in detail the features you expect.
title: "\"\U0001F680 一些功能。。。 | [Feature]Some feature...\""
labels: enhancement
assignees: ''
---
<!-- 请在您提交期望的功能之前,回答以下这些问题。 | Please answer these questions before you submit the desired feature. -->
#### 您使用的场景? | 1. Your usage scenarios?
#### 您期望的结果是怎样的? | 2. What is your expected outcome?
Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

-322
View File
@@ -1,322 +0,0 @@
name: Build OpenWrt Packages
on:
push:
branches: [ "main", "master" ]
tags: [ "v*" ]
pull_request:
branches: [ "main", "master" ]
workflow_dispatch:
env:
PACKAGE_NAME: luci-app-wechatpush
SDK_DIR: /home/runner/work/openwrt-sdk
permissions:
contents: write
jobs:
build-apk:
name: Build APK (v25.12)
runs-on: ubuntu-latest
env:
OPENWRT_VERSION: 'v25.12.0-rc1'
strategy:
fail-fast: false
matrix:
target:
- name: all
path: x86/64
steps:
- name: Checkout package repository
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential rsync unzip zstd python3-pyelftools
- name: Cache OpenWrt SDK
id: cache-sdk
uses: actions/cache@v4
with:
path: ${{ env.SDK_DIR }}
key: ${{ runner.os }}-sdk-apk-${{ matrix.target.name }}-${{ env.OPENWRT_VERSION }}
- name: Download, extract OpenWrt SDK and update feeds
if: steps.cache-sdk.outputs.cache-hit != 'true'
run: |
release_version="${OPENWRT_VERSION#v}"
sdk_url_prefix="https://downloads.openwrt.org/releases/${release_version}/targets/${{ matrix.target.path }}"
sdk_filename=$(curl -s "$sdk_url_prefix/" | \
grep -o '<a href="openwrt-sdk-.*-x86_64\.tar\.\(xz\|zst\)">' | \
sed -e 's/.*<a href="//' -e 's/">//' | \
head -n 1)
if [ -z "$sdk_filename" ]; then
echo "::error::Could not find SDK file at ${sdk_url_prefix}/"
echo "Available files:"
curl -s "$sdk_url_prefix/" | grep -o '<a href="[^"]*">[^<]*</a>' | sed -e 's/<a href="\(.*\)".*>\(.*\)/\1\t\2/'
exit 1
fi
echo "Downloading SDK: ${sdk_url_prefix}/${sdk_filename}"
wget -q "${sdk_url_prefix}/${sdk_filename}"
echo "Extracting SDK..."
tar -xf "$sdk_filename"
mv $(find . -maxdepth 1 -type d -name 'openwrt-sdk-*') ${{ env.SDK_DIR }}
echo "Updating feeds..."
cd ${{ env.SDK_DIR }}
echo "Original feeds configuration:"
cat feeds.conf.default
max_retries=5
retry_count=0
while [ $retry_count -lt $max_retries ]; do
echo "Attempt $((retry_count + 1)) of $max_retries to update feeds..."
echo "Replacing feeds URLs with GitHub mirrors..."
sed -i 's|https://git.openwrt.org/openwrt/openwrt.git|https://github.com/openwrt/openwrt.git|g' feeds.conf.default
sed -i 's|https://git.openwrt.org/feed/packages.git|https://github.com/openwrt/packages.git|g' feeds.conf.default
sed -i 's|https://git.openwrt.org/project/luci.git|https://github.com/openwrt/luci.git|g' feeds.conf.default
sed -i 's|https://git.openwrt.org/feed/routing.git|https://github.com/openwrt/routing.git|g' feeds.conf.default
sed -i 's|https://git.openwrt.org/feed/telephony.git|https://github.com/openwrt/telephony.git|g' feeds.conf.default
if [ $retry_count -eq 0 ]; then
echo "Updated feeds configuration:"
cat feeds.conf.default
fi
if ./scripts/feeds update -a; then
echo "Feeds update successful"
break
else
retry_count=$((retry_count + 1))
if [ $retry_count -lt $max_retries ]; then
wait_time=$((60 * retry_count))
echo "Feeds update failed, waiting ${wait_time} seconds before retry..."
sleep $wait_time
else
echo "::error::Feeds update failed after $max_retries attempts"
exit 1
fi
fi
done
./scripts/feeds install -a
cd ..
- name: Prepare build environment
working-directory: ${{ env.SDK_DIR }}
run: |
rm -rf package/${PACKAGE_NAME}
src_dir="${{ github.workspace }}"
if [ -f "$src_dir/Makefile" ]; then
pkg_src="$src_dir"
else
pkg_src=$(find "$src_dir" -maxdepth 1 -type d -name "${PACKAGE_NAME}" -o -name "luci-app-*${PACKAGE_NAME#luci-app-}*" | head -n 1)
if [ -z "$pkg_src" ]; then
echo "::error::Could not locate package directory for ${PACKAGE_NAME}"
exit 1
fi
fi
mkdir -p ./package/${PACKAGE_NAME}
rsync -a --delete "$pkg_src"/ ./package/${PACKAGE_NAME}/
# 注意:wrtbwmon 在 OpenWrt 22+ (firewall4/nftables) 环境下不兼容
# 流量统计功能在新版本中不可用,但保留其他功能
translation_pkg=${PACKAGE_NAME#luci-app-}
mkdir -p ./bin/packages
find ./bin/packages -type f -name "${PACKAGE_NAME}*.apk" -delete
find ./bin/packages -type f -name "luci-i18n-${translation_pkg}-*.apk" -delete
{
echo "CONFIG_PACKAGE_${PACKAGE_NAME}=m"
# 注意:流量监控功能在 OpenWrt 22+ 中不可用(wrtbwmon 与 firewall4 不兼容)
# echo "CONFIG_PACKAGE_${PACKAGE_NAME}_Enable_Traffic_Monitoring=y"
echo "CONFIG_PACKAGE_${PACKAGE_NAME}_Enable_Local_Disk_Information_Detection=y"
echo "CONFIG_PACKAGE_${PACKAGE_NAME}_Enable_Host_Information_Detection=y"
# echo "CONFIG_PACKAGE_wrtbwmon=m" # 在 firewall4 环境下不兼容
echo "CONFIG_PACKAGE_lsblk=m"
echo "CONFIG_PACKAGE_smartmontools=m"
echo "CONFIG_PACKAGE_smartmontools-drivedb=m"
echo "CONFIG_PACKAGE_openssh-client=m"
echo "CONFIG_PACKAGE_openssh-keygen=m"
echo "CONFIG_PACKAGE_luci-i18n-${translation_pkg}-zh-cn=m"
echo "CONFIG_LUCI_LANG_zh_Hans=y"
} > .config
make defconfig
- name: Compile the package
working-directory: ${{ env.SDK_DIR }}
run: |
make package/${PACKAGE_NAME}/compile V=s -j$(($(nproc) + 1))
- name: Find and prepare .apk file
id: find_apk
working-directory: ${{ env.SDK_DIR }}
run: |
translation_pkg=${PACKAGE_NAME#luci-app-}
rm -rf upload
mkdir -p upload
find ./bin/packages -type f \( -name "${PACKAGE_NAME}*.apk" -o -name "luci-i18n-${translation_pkg}-*.apk" -o -name "lsblk*.apk" -o -name "smartmontools*.apk" -o -name "openssh-client*.apk" -o -name "openssh-keygen*.apk" \) | while read -r apk; do
rel_path=${apk#./}
echo "Found apk: $rel_path"
cp "$apk" upload/
done
if ! find upload -maxdepth 1 -type f -name '*.apk' | grep -q '.'; then
echo "::error::No apk files found in ./bin/packages/"
echo "Directory tree of ./bin/:"
ls -R ./bin/
exit 1
fi
echo "path=upload" >> $GITHUB_OUTPUT
- name: Upload .apk artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.PACKAGE_NAME }}-apk-${{ matrix.target.name }}
path: ${{ env.SDK_DIR }}/${{ steps.find_apk.outputs.path }}
if-no-files-found: error
build-ipk:
name: Build IPK (v23.05)
runs-on: ubuntu-latest
env:
OPENWRT_VERSION: 'v23.05.3'
strategy:
fail-fast: false
matrix:
target:
- name: all
path: x86/64
steps:
- name: Checkout package repository
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential rsync unzip zstd
- name: Cache OpenWrt SDK
id: cache-sdk
uses: actions/cache@v4
with:
path: ${{ env.SDK_DIR }}
key: ${{ runner.os }}-sdk-ipk-${{ matrix.target.name }}-${{ env.OPENWRT_VERSION }}
- name: Download, extract OpenWrt SDK and update feeds
if: steps.cache-sdk.outputs.cache-hit != 'true'
run: |
release_version="${OPENWRT_VERSION#v}"
sdk_url_prefix="https://downloads.openwrt.org/releases/${release_version}/targets/${{ matrix.target.path }}"
sdk_filename=$(curl -s "$sdk_url_prefix/" | \
grep -o '<a href="openwrt-sdk-.*-x86_64\.tar\.\(xz\|zst\)">' | \
sed -e 's/.*<a href="//' -e 's/">//' | \
head -n 1)
if [ -z "$sdk_filename" ]; then
echo "::error::Could not find SDK file at ${sdk_url_prefix}/"
echo "Available files:"
curl -s "$sdk_url_prefix/" | grep -o '<a href="[^"]*">[^<]*</a>' | sed -e 's/<a href="\(.*\)".*>\(.*\)/\1\t\2/'
exit 1
fi
echo "Downloading SDK: ${sdk_url_prefix}/${sdk_filename}"
wget -q "${sdk_url_prefix}/${sdk_filename}"
echo "Extracting SDK..."
tar -xf "$sdk_filename"
mv $(find . -maxdepth 1 -type d -name 'openwrt-sdk-*') ${{ env.SDK_DIR }}
echo "Updating feeds..."
cd ${{ env.SDK_DIR }}
./scripts/feeds update -a
./scripts/feeds install -a
cd ..
- name: Prepare build environment
working-directory: ${{ env.SDK_DIR }}
run: |
rm -rf package/${PACKAGE_NAME}
src_dir="${{ github.workspace }}"
if [ -f "$src_dir/Makefile" ]; then
pkg_src="$src_dir"
else
pkg_src=$(find "$src_dir" -maxdepth 1 -type d -name "${PACKAGE_NAME}" -o -name "luci-app-*${PACKAGE_NAME#luci-app-}*" | head -n 1)
if [ -z "$pkg_src" ]; then
echo "::error::Could not locate package directory for ${PACKAGE_NAME}"
exit 1
fi
fi
mkdir -p ./package/${PACKAGE_NAME}
rsync -a --delete "$pkg_src"/ ./package/${PACKAGE_NAME}/
translation_pkg=${PACKAGE_NAME#luci-app-}
mkdir -p ./bin/packages
find ./bin/packages -type f -name "${PACKAGE_NAME}*.ipk" -delete
find ./bin/packages -type f -name "luci-i18n-${translation_pkg}-*.ipk" -delete
{
echo "CONFIG_PACKAGE_${PACKAGE_NAME}=m"
echo "CONFIG_PACKAGE_luci-i18n-${translation_pkg}-zh-cn=m"
echo "CONFIG_LUCI_LANG_zh_Hans=y"
} > .config
make defconfig
- name: Compile the package
working-directory: ${{ env.SDK_DIR }}
run: |
make package/${PACKAGE_NAME}/compile V=s -j$(($(nproc) + 1))
- name: Find and prepare .ipk file
id: find_ipk
working-directory: ${{ env.SDK_DIR }}
run: |
translation_pkg=${PACKAGE_NAME#luci-app-}
rm -rf upload
mkdir -p upload
find ./bin/packages -type f \( -name "${PACKAGE_NAME}*.ipk" -o -name "luci-i18n-${translation_pkg}-*.ipk" \) | while read -r ipk; do
rel_path=${ipk#./}
echo "Found IPK: $rel_path"
cp "$ipk" upload/
done
if ! find upload -maxdepth 1 -type f -name '*.ipk' | grep -q '.'; then
echo "::error::No IPK files found in ./bin/packages/"
echo "Directory tree of ./bin/:"
ls -R ./bin/
exit 1
fi
echo "path=upload" >> $GITHUB_OUTPUT
- name: Upload .ipk artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.PACKAGE_NAME }}-ipk-${{ matrix.target.name }}
path: ${{ env.SDK_DIR }}/${{ steps.find_ipk.outputs.path }}
if-no-files-found: error
release:
name: Publish Release
runs-on: ubuntu-latest
needs: [build-apk, build-ipk]
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: release-assets
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
generate_release_notes: true
files: release-assets/**/*
-339
View File
@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
-61
View File
@@ -1,61 +0,0 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-wechatpush
PKG_VERSION:=3.6.12
PKG_RELEASE:=
PKG_MAINTAINER:=tty228 <tty228@yeah.net>
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Enable_Traffic_Monitoring \
CONFIG_PACKAGE_$(PKG_NAME)_Enable_Local_Disk_Information_Detection \
CONFIG_PACKAGE_$(PKG_NAME)_Enable_Host_Information_Detection
LUCI_TITLE:=LuCI support for wechatpush
LUCI_PKGARCH:=all
LUCI_DEPENDS:=+iputils-arping +curl +jq +bash +luci-lua-runtime \
+PACKAGE_$(PKG_NAME)_Enable_Traffic_Monitoring:wrtbwmon \
+PACKAGE_$(PKG_NAME)_Enable_Local_Disk_Information_Detection:lsblk \
+PACKAGE_$(PKG_NAME)_Enable_Local_Disk_Information_Detection:smartmontools \
+PACKAGE_$(PKG_NAME)_Enable_Local_Disk_Information_Detection:smartmontools-drivedb \
+PACKAGE_$(PKG_NAME)_Enable_Host_Information_Detection:openssh-client \
+PACKAGE_$(PKG_NAME)_Enable_Host_Information_Detection:openssh-keygen
define Package/$(PKG_NAME)/config
config PACKAGE_$(PKG_NAME)_Enable_Traffic_Monitoring
bool "Enable Traffic Monitoring"
help
The traffic statistics feature relies on the wrtbwmon package. This plugin may conflict with Routing/NAT, Flow Offloading, and proxy internet access plugins, potentially leading to an inability to retrieve traffic information.
select PACKAGE_wrtbwmon
depends on PACKAGE_$(PKG_NAME)
default n
config PACKAGE_$(PKG_NAME)_Local_Disk_Information_Detection
bool "Local Disk Information Detection"
help
If the lsblk package is not installed, the total disk capacity information might be inconsistent with actual values. When smartctl is not installed, information about disk temperature, power-on time, health status, and more will be unavailable. If you are using a virtual machine or have not installed a physical disk, this feature is typically unnecessary.
select PACKAGE_lsblk
select PACKAGE_smartmontools
select PACKAGE_smartmontools-drivedb
depends on PACKAGE_$(PKG_NAME)
default n
config PACKAGE_$(PKG_NAME)_Host_Information_Detection
bool "Host Information Detection"
help
When using a virtual machine and requiring information about the host machine's temperature and disk details, you need to install openssh-client and openssh-keygen for SSH connections.
select PACKAGE_openssh-client
select PACKAGE_openssh-keygen
depends on PACKAGE_$(PKG_NAME)
default n
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/wechatpush
/usr/share/wechatpush/api/diy.json
/usr/share/wechatpush/api/logo.jpg
/usr/share/wechatpush/api/ipv4.list
/usr/share/wechatpush/api/ipv6.list
/usr/share/wechatpush/api/device_aliases.list
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
-91
View File
@@ -1,91 +0,0 @@
## 简介
[![Lastest Release](https://img.shields.io/github/release/tty228/luci-app-wechatpush.svg?style=flat)](https://github.com/tty228/luci-app-wechatpush/releases)
[![GitHub All Releases](https://img.shields.io/github/downloads/tty228/luci-app-wechatpush/total)](https://github.com/tty228/luci-app-wechatpush/releases)
[中文文档](README.md) | [English](README_en.md)
这是一款用于 OpenWRT 路由器上进行 微信、Telegram、邮件 通知的插件。
主要功能:
- [x] 路由 IP、IPv6 变动推送
- [x] 设备 上线、离线 推送
- [x] 设备在线列表及流量使用情况
- [x] CPU 负载、温度监视、PVE 宿主机温度监控
- [x] 路由运行状态定时推送
- [x] 路由 Web、SSH 登录提示,自动拉黑、端口敲门
- [x] 无人值守任务
通知服务支持列表:
| 推送应用 | 方式 | 接口说明 |
| :-------- | :----- | :----- |
| 微信 | Server酱 | https://sct.ftqq.com/
| 微信 | 推送加 | http://www.pushplus.plus/
| 微信等 | WPUSH | https://wpush.cn/
| 微信 | WxPusher | https://wxpusher.zjiecode.com/docs
| 企业微信 | 应用推送 | https://work.weixin.qq.com/api/doc/90000/90135/90248
| Telegram | bot | https://t.me/BotFather
| Mail | msmtp | https://marlam.de/msmtp/
精力有限,如需要钉钉推送、飞书推送、Bark 推送等请尝试另一个分支 https://github.com/zzsj0928/luci-app-pushbot ,或使用自定义 API 设置
v2.06.2 之后的版本不再支持 LuCI 18.06,如需编译,请使用 openwrt-18.06 分支,拉取源码时请使用 `git clone -b openwrt-18.06 https://github.com/tty228/luci-app-wechatpush.git` 命令
## 说明
**关于安装:**
插件依赖 iputils-arping + curl + jq + bash,对于内存有限的路由器,请酌情安装,**在安装之前,请先运行 `opkg update` 命令,以便在安装过程中安装依赖。**
基于 X86 OpenWrt v23.05.0 制作,不同系统不同设备,可能会遇到各种问题,**如获取到错误的温度信息、页面显示错误、报错等,自行适配**
**关于主机名:**
对于设备未宣告主机名、光猫拨号上网、OpenWrt 作为旁路网关等各类情况导致的获取主机名失败,可以通过以下方式设置主机名
- 使用设备名备注
- 在高级设置处配置从光猫、小米路由器、ROS 获取
- 开启 MAC 设备数据库
- 安装 samba*-server 或 samba*-client,使程序可以通过 NetBIOS 查询主机名
**关于设备在线状态:**
默认使用 ping/arping 来主动探测设备在线状态,以对抗 Wi-Fi 休眠机制,主动探测较为耗时但可以获得较为精准的设备在线状态
- 如遇设备休眠频繁,请在高级设置处自行调整超时设置
- 如果不需要太过精准的设备在线信息,只需要其余功能,可以在高级设置中关闭主动探测
**关于流量统计信息:**
流量统计功能依赖 wrtbwmon ,需自行选装或编译,**该插件与 Routing/NAT 、Flow Offloading 、代理上网等插件冲突,开启后将会无法获取流量,请自行选择**
**关于硬盘信息:**
当 OpenWrt 系统或远程主机(PVE)未安装 lsblk 时,硬盘容量信息可能与实际不一致
当 OpenWrt 系统或远程主机(PVE)未安装 smartctl 时,硬盘温度、通电时间、健康度等信息不可用
**关于 bug 提交:**
提交 bug 时请尽量带上以下信息
- 设备信息及插件版本号
- 执行 `/usr/share/wechatpush/wechatpush` 后的提示信息
- 报错后的日志信息、`/tmp/wechatpush/` 目录下的文件信息
- `bash -x /usr/share/wechatpush/wechatpush t1` 的详细运行信息
## 下载
| 支持的 OpenWrt 版本 | 下载地址 |
| :-------- | :----- |
| openwrt-19.07.0 ... latest | [![Lastest Release](https://img.shields.io/github/release/tty228/luci-app-wechatpush.svg?style=flat)](https://github.com/tty228/luci-app-wechatpush/releases)
| openwrt-18.06 | [![Release v2.06.2](https://img.shields.io/badge/release-v2.06.2-lightgrey.svg)](https://github.com/tty228/luci-app-wechatpush/releases/tag/v2.06.2)
## 捐赠
如果你觉得此项目对你有帮助,请捐助我们,使项目能持续发展和更加完善。
![image](https://github.com/tty228/luci-app-wechatpush/blob/master/.github/others/20260303202537_34_664.png)
-89
View File
@@ -1,89 +0,0 @@
# Introduction
[![Lastest Release](https://img.shields.io/github/release/tty228/luci-app-wechatpush.svg?style=flat)](https://github.com/tty228/luci-app-wechatpush/releases)
[![GitHub All Releases](https://img.shields.io/github/downloads/tty228/luci-app-wechatpush/total)](https://github.com/tty228/luci-app-wechatpush/releases)
[中文文档](README.md) | [English](README_en.md)
**OpenWrt Notification Plugin – Send Alerts to WeChat or Telegram**
This OpenWrt plugin delivers real-time router notifications to your mobile device via **WeChat** or **Telegram** or **Mail**. Monitor your network with alerts for:
- [x] IP changes (IPv4/IPv6)
- [x] Device connections (online/offline)
- [x] Online client list & traffic usage
- [x] CPU load & temperature (including virtualized hosts like PVE)
- [x] Router status reports
- [x] Security alerts (failed web/SSH logins with auto blacklist)
- [x] Port knocking & automated tasks
Supported services:
| Push application | Method | description |
| :-------- | :----- | :----- |
| WeChat | Server Chan | https://sct.ftqq.com/
| WeChat | PushPlus | http://www.pushplus.plus/
| WeChat etc. | WPUSH | https://wpush.cn/
| WeChat | WxPusher | https://wxpusher.zjiecode.com/docs
| WeChat for Enterprise | Application Push | https://work.weixin.qq.com/api/doc/90000/90135/90248
| Telegram | bot | https://t.me/BotFather
| Mail | msmtp | https://marlam.de/msmtp/
Limited resources are available. If you need services such as DingTalk push, Feishu push, Bark push, etc., please try another branch at https://github.com/zzsj0928/luci-app-pushbot, or use **custom API settings**.
## Instructions
**Regarding Installation:**
The plugin requires dependencies on iputils-arping + curl + jq + bash. For routers with limited memory, please consider the installation carefully. **Before installing, please run the opkg update command to install dependencies during the installation process.**
Developed based on X86 OpenWrt v23.05.0, different systems and devices may encounter various issues. **If you encounter errors in temperature information retrieval, display errors, or other issues, please adapt accordingly.**
**Regarding Hostnames:**
For devices that do not declare hostnames, devices connected via optical modem dial-up, OpenWrt used as a bypass gateway, and other scenarios where hostname retrieval fails, you can set the hostname using the following methods:
- Use device name remarks.
- Configure to obtain the hostname from the optical modem in advanced settings.
- Enable MAC device database.
- Install samba*-server or samba*-client to enable the program to query hostnames via NetBIOS.
**Regarding Device Online Status:**
By default, ping/arping is used to actively detect device online status to counter Wi-Fi sleep mechanism. Active detection takes more time but provides more accurate device online status.
- If devices frequently go into sleep mode, please adjust the timeout settings in advanced settings.
- If you don't require highly precise device online information and only need other features, you can disable active detection in advanced settings.
**Regarding Traffic Statistics:**
Traffic statistics functionality depends on `wrtbwmon`. Please install or compile it yourself. **Enabling this plugin will conflict with Routing/NAT, Flow Offloading, proxy internet access, and other plugins, resulting in the inability to obtain traffic statistics. Please choose accordingly.**
**About Hard Drive Information:**
When the OpenWrt system or remote host (PVE) does not have lsblk installed, the hard drive capacity information may be inconsistent with the actual capacity.
When the OpenWrt system or remote host (PVE) does not have smartctl installed, information such as hard drive temperature, uptime, and health status will not be available.
**Regarding Bug Submissions:**
When submitting a bug, please provide the following information if possible:
- Device information and plugin version number.
- Prompt information after executing `/usr/share/wechatpush/wechatpush`.
- Log information and file information in the `/tmp/wechatpush/` directory after encountering an error.
- Detailed execution information of `bash -x /usr/share/wechatpush/wechatpush t1`.
## DownLoad
| Supported OpenWrt Versions | Download Link |
| :-------- | :----- |
| openwrt-19.07.0 ... latest | [![Lastest Release](https://img.shields.io/github/release/tty228/luci-app-wechatpush.svg?style=flat)](https://github.com/tty228/luci-app-wechatpush/releases)
| openwrt-18.06 | [![Release v2.06.2](https://img.shields.io/badge/release-v2.06.2-lightgrey.svg)](https://github.com/tty228/luci-app-wechatpush/releases/tag/v2.06.2)
## Donate
If you feel that this project is helpful to you, please donate to us so that the project can continue to develop and be more perfect.
![image](https://github.com/tty228/luci-app-wechatpush/blob/master/.github/others/20260303202537_34_664.png)
@@ -1,315 +0,0 @@
'use strict';
'require view';
'require fs';
'require ui';
'require uci';
'require rpc';
'require form';
'require poll';
return view.extend({
callHostHints: rpc.declare({
object: 'luci-rpc',
method: 'getHostHints',
expect: { '': {} }
}),
load: function () {
return Promise.all([
this.callHostHints()
]);
},
parseHostHints: function (data) {
var hosts = [];
Object.keys(data || {}).forEach(function (mac) {
var hint = data[mac] || {},
ipaddrs = hint.ipaddrs || hint.ipv4 || [];
if (!Array.isArray(ipaddrs))
ipaddrs = [ ipaddrs ];
ipaddrs.forEach(function (ip) {
var octets = (typeof(ip) === 'string' && /^\d{1,3}(\.\d{1,3}){3}$/.test(ip))
? ip.split('.').map(Number) : [];
if (octets.length === 4 && octets.every(function (octet) {
return octet >= 0 && octet <= 255;
})) {
hosts.push({
ip: ip,
mac: mac
});
}
});
});
// Sort hosts array by IP address
hosts.sort(function (a, b) {
var ipA = a.ip.split('.').map(Number);
var ipB = b.ip.split('.').map(Number);
for (var i = 0; i < 4; i++) {
if (ipA[i] !== ipB[i]) {
return ipA[i] - ipB[i];
}
}
return 0;
});
return hosts;
},
formatHostIPMAC: function (host) {
return host.ip + ' (' + host.mac + ')';
},
formatHostMACIP: function (host) {
return host.mac + ' (' + host.ip + ')';
},
render: function (data) {
var hosts = this.parseHostHints(data[0]),
m, s, o,
programPath = '/usr/share/wechatpush/wechatpush';
m = new form.Map('wechatpush', _(''))
m.description = _("If you are not familiar with the meanings of these options, please do not modify them.<br/><br/>")
s = m.section(form.NamedSection, 'config', 'wechatpush', _(''));
s.anonymous = true
s.addremove = false
o = s.option(form.Flag, "passive_mode", _("Disable active detection"))
o.default = 0
o.rmempty = true
o.description = _("Disable active detection of client online status. Enabling this feature will no longer prompt device online/offline events.<br/>Suitable for users who are not sensitive to online devices but need other features.")
o = s.option(form.Value, "thread_num", _('Maximum concurrent processes'))
o.placeholder = "3"
o.datatype = "uinteger"
o.rmempty = false;
o.description = _("Do not change the setting value for low-performance devices, or reduce the parameters as appropriate.")
o = s.option(form.ListValue, "defaultSortColumn", _("Client list sorting method"))
o.default = "ip"
o.value("ip", _("IP"))
o.value("uptime", _("Online time"))
o.description = _("This will change the sorting method for both the online device list page and the sorting order in the push content.")
o = s.option(form.Value, "soc_code", _('Custom temperature reading command'))
o.rmempty = true
o.value("", _("Default"))
o.value("pve", _("Proxmox Virtual Environment"))
o.description = _("If you need to use special symbols such as quotes, $, !, etc. in custom commands, you need to escape them yourself.<br/>You can use the command eval echo $(uci get wechatpush.wechatpush.soc_code) to view command output and error information.<br/>The execution result should be a pure number (including decimals) for temperature comparison.<br/>Here is an example that does not require escaping:<br/>cat /sys/class/thermal/thermal_zone0/temp|sort -nr|head -n1|cut -c-2")
o = s.option(form.Value, "server_host", _("Host machine address"))
o.rmempty = true
o.default = "10.0.0.2"
o.depends('soc_code', 'pve');
o = s.option(form.Value, "server_port", _("Host machine SSH port"))
o.rmempty = true
o.default = "22"
o.description = _('The default SSH port is 22. If you have a custom port, please fill in the custom SSH port.<br/>Please make sure you have set up key-based login, otherwise it may cause script errors.<br/>Install the sensors command on PVE by searching on the internet.<br/>Example for key-based login (modify the address and port number accordingly):<br/>opkg update # Update package list<br/>opkg install openssh-client openssh-keygen # Install openssh client<br/>echo -e \"\\n\" | ssh-keygen -t rsa # Generate key file (no passphrase)<br/>pve_host=`uci get wechatpush.config.server_host` || pve_host=\"10.0.0.3\" # Read the PVE host address from the configuration file, If not saved, please fill in by yourself.<br/>pve_port=`uci get wechatpush.config.server_port` || pve_host=\"22\" # Read the PVE host SSH port number from the configuration file, If not saved, please fill in by yourself.<br/>ssh -o StrictHostKeyChecking=yes root@${pve_host} -p ${pve_port} \"tee -a ~/.ssh/OpenWrt_id_rsa.pub\" < ~/.ssh/id_rsa.pub # Transfer public key to PVE<br/>ssh root@${pve_host} -p ${pve_port} \"cat ~/.ssh/OpenWrt_id_rsa.pub >> ~/.ssh/authorized_keys\" # Write public key to PVE<br/>ssh -i /root/.ssh/id_rsa root@${pve_host} -p ${pve_port} sensors # To avoid script errors during the initial connection, please use a private key to connect to PVE and test the temperature command for its proper functioning.<br/>For users who frequently flash firmware, please add /root/.ssh/ to the backup list to avoid duplicate operations.');
// cat ~/.ssh/id_rsa.pub | ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@${pve_host} -p ${pve_port} "cat >> ~/.ssh/authorized_keys"
o.depends('soc_code', 'pve');
o = s.option(form.Button, '_soc', _('Test temperature command'), _('You may need to save the configuration before sending.'));
o.inputstyle = 'action';
o.onclick = function () {
var _this = this;
return fs.exec(programPath, ['soc']).then(function (res) {
if (!res.stdout) {
throw new Error(_('Returned value is empty'));
}
_this.description = res.stdout.trim();
return _this.map.reset();
}).catch(function (err) {
_this.description = _('Fetch failed:') + err.message;
return _this.map.reset();
});
};
o = s.option(form.Value, 'up_timeout', _('Device online detection timeout (s)'));
o.placeholder = "2"
o.optional = false
o.datatype = "uinteger"
o.rmempty = false;
o.depends('passive_mode', '0');
o = s.option(form.Value, "down_timeout", _('Device offline detection timeout (s)'))
o.placeholder = "10"
o.optional = false
o.datatype = "uinteger"
o.rmempty = false;
o.depends('passive_mode', '0');
o = s.option(form.Value, "timeout_retry_count", _('Offline detection count'))
o.placeholder = "2"
o.optional = false
o.datatype = "uinteger"
o.rmempty = false;
o.description = _("If the device has good signal strength and no Wi-Fi sleep issues, you can reduce the above values.<br/>Due to the mysterious nature of Wi-Fi sleep during the night, if you encounter frequent disconnections, please adjust the parameters accordingly.<br/>..╮(╯_╰)╭..")
o.depends('passive_mode', '0');
o = s.option(form.Flag, "only_timeout_push", _("Offline timeout applies only to the devices that receive push notifications"))
o.default = 0
o.rmempty = true
o.description = _("When this option is selected, the offline timeout and offline detection count apply only to the devices that require push notifications. Other devices will use default values, which can significantly reduce the time required for detection. However, it may result in inaccurate online time displayed in the online devices list. It is recommended to enable this option only when there are many devices and frequent offline occurrences are observed for specific devices of interest.")
o.depends('passive_mode', '0');
o = s.option(form.DynamicList, 'always_check_ip_list', _('IP address to always scan'));
o.datatype = 'ipaddr';
o.description = _('The IPs in the list are always subjected to online detection regardless of whether they exist in the ARP list, suitable for secondary routing scenarios.');
hosts.forEach(function (host) {
o.value(host.ip, this.formatHostIPMAC(host));
}, this);
o.depends('passive_mode', '0');
o = s.option(form.MultiValue, 'device_info_helper', _('Assist in obtaining device information'));
o.value('gateway_info', _('Retrieve hostname list from modem'));
o.value('miwifi_info', _('Get wireless band information and hostname from MiWiFi'));
o.value('mikrotik_info', _('Retrieve hostname list from modem MikroTik Router'));
o.value('openwrt_info', _('Get wireless band information and hostname from other OpenWrt'));
o.value('scan_local_ip', _('Scan local IP'));
o.modalonly = true;
o.description = _('When OpenWrt is used as a bypass gateway and cannot obtain device hostnames or a complete list of local network devices.<br/>the \"Retrieve hostname list from modem\" option has only been tested with HG5143F/HN8145V China Telecom gateways and may not be universally applicable.<br/>The \"Scan local IP\" option may not retrieve hostnames, so please use device name annotations in conjunction with it.');
o.depends('passive_mode', '0');
o = s.option(form.Value, "gateway_host_url", _('Optical modem login URL'));
o.rmempty = true;
o.default = "http://192.168.1.1/cgi-bin/luci";
o.depends({ device_info_helper: "gateway_info", '!contains': true });
o = s.option(form.Value, "gateway_info_url", _('Device list JSON URL'));
o.rmempty = true;
o.default = "http://192.168.1.1/cgi-bin/luci/admin/allInfo";
o.description = _('Use F12 console to capture<br/>ip, devName, model are mandatory fields. Example JSON file information:<br/>{\"pc1\":{\"devName\":\"RouterOS\",\"model\":\"\",\"type\":\"pc\",\"ip\":\"192.168.1.7\"}}');
o.depends({ device_info_helper: "gateway_info", '!contains': true });
o = s.option(form.Value, "gateway_logout_url", _('Optical modem logout URL'))
o.rmempty = true
o.default = "http://192.168.1.1/cgi-bin/luci/admin/logout"
o.description = _("Not a mandatory field, but it may affect other users logging into the web management page, e.g., HG5143F")
o.depends({ device_info_helper: "gateway_info", '!contains': true });
o = s.option(form.Value, "gateway_username_id", _('Login page account input box ID'))
o.rmempty = true
o.default = "username"
o.depends({ device_info_helper: "gateway_info", '!contains': true });
o = s.option(form.Value, "gateway_password_id", _('Login page password input box ID'))
o.rmempty = true
o.default = "psd"
o.description = _("Right-click in the browser and select 'Inspect Element'")
o.depends({ device_info_helper: "gateway_info", '!contains': true });
o = s.option(form.Value, "gateway_username", _('Optical modem login account'))
o.rmempty = true
o.default = "useradmin"
o.depends({ device_info_helper: "gateway_info", '!contains': true });
o = s.option(form.Value, "gateway_password", _('Optical modem login password'))
o.rmempty = true
o.description = _("Use a regular account, no need for super password")
o.depends({ device_info_helper: "gateway_info", '!contains': true });
o = s.option(form.Value, "miwifi_ip", _('MiWiFi IP Address'));
o.rmempty = true;
o.description = _("The main router address is all that is needed in the Mesh wireless network topology.")
o.depends({ device_info_helper: "miwifi_info", '!contains': true });
o = s.option(form.Value, "miwifi_password", _('MiWiFi Login Password'))
o.rmempty = true
o.depends({ device_info_helper: "miwifi_info", '!contains': true });
o = s.option(form.Value, "mikrotik_ip", _('MikroTik Routers IP Address'));
o.rmempty = true;
o.description = _('echo -e "\\n" | ssh-keygen -t rsa -f /root/.ssh/id_rsa -N ""<br/>scp /root/.ssh/id_rsa.pub your_username@mikrotik_ip:/id_rsa.pub<br/>ssh your_username@mikrotik_ip<br/>/user ssh-keys import public-key-file=id_rsa.pub user=your_username')
o.depends({ device_info_helper: "mikrotik_info", '!contains': true });
o = s.option(form.Value, "mikrotik_username", _('MikroTik Router Account'))
o.rmempty = true
o.depends({ device_info_helper: "mikrotik_info", '!contains': true });
o = s.option(form.DynamicList, "op_host_ips", _('OpenWrt IP Address'));
o.rmempty = true;
o.description = _('echo -e "\\n" | ssh-keygen -t rsa -f /root/.ssh/id_rsa -N ""<br/>ssh root@your_openwrt_ip "mkdir -p /root/.ssh && chmod 700 /root/.ssh && echo $(cat /root/.ssh/id_rsa.pub) >> /etc/dropbear/authorized_keys && chmod 600 /etc/dropbear/authorized_keys"')
o.depends({ device_info_helper: "openwrt_info", '!contains': true });
o = s.option(form.Value, "scan_ip_range", _('IP range to be scanned'))
o.rmempty = true
o.placeholder = _('192.168.1.1-192.168.1.100');
o.depends({ device_info_helper: "scan_local_ip", '!contains': true });
o = s.option(form.Value, 'device_info_helper_sleeptime', _('Interval for capturing info'));
o.rmempty = false;
o.placeholder = '600';
o.datatype = 'and(uinteger,min(60))'
o.description = _("Generally, frequent capturing is not necessary. Adjust it as needed.")
o.depends({ device_info_helper: "gateway_info", '!contains': true });
o.depends({ device_info_helper: "miwifi_info", '!contains': true });
o.depends({ device_info_helper: "mikrotik_info", '!contains': true });
o.depends({ device_info_helper: "openwrt_info", '!contains': true });
o.depends({ device_info_helper: "scan_local_ip", '!contains': true });
o = s.option(form.Flag, "unattended_enable", _("Unattended tasks"))
o.default = 0
o.rmempty = true
o.description = _("Please make sure the script can run properly, otherwise it may cause frequent restarts and other errors!")
o = s.option(form.Flag, 'zerotier_helper', _('Restart zerotier after IP change'));
o.description = _('An old issue with zerotier<br/>Cannot reconnect after disconnection, emmm, I don\'t know if it has been fixed now.');
o.depends('unattended_enable', '1');
o = s.option(form.Flag, "unattended_only_on_disturb_time", _("Redial only during Do-Not-Disturb period"))
o.default = 0
o.rmempty = true
o.description = _("Avoid redialing network during the day to prevent waiting for DDNS domain resolution. This feature does not affect disconnection detection.<br/>Due to the issue of certain apps consuming excessive data at night, this feature may be unstable.")
o.depends('unattended_enable', '1');
o = s.option(form.DynamicList, 'unattended_device_aliases', _('Followed device list'));
o.datatype = 'macaddr';
o.description = _("Will only be executed when none of the devices in the list are online.<br/>After an hour of Do-Not-Disturb period, if the devices in the focus list have low traffic (around 100kb/m) for five minutes, they will be considered offline.")
o.depends('unattended_enable', '1');
hosts.forEach(function (host) {
o.value(host.mac, this.formatHostMACIP(host));
}, this);
o = s.option(form.ListValue, "network_disconnect_event", _("When the network is disconnected"))
o.default = ""
o.value("", _("No operation"))
o.value("1", _("Restart the router"))
o.value("2", _("Redialing network"))
o.description = _("The restart operation will occur ten minutes after the network disconnection and will be attempted a maximum of two times. If the option to log in to the optical modem is available, this operation will attempt to restart the optical modem.<br/>【!!This feature cannot guarantee compatibility!!】")
o.depends('unattended_enable', '1');
o = s.option(form.ListValue, "unattended_autoreboot_mode", _("Scheduled reboot"))
o.default = ""
o.value("", _("No operation"))
o.value("1", _("Restart the router"))
o.value("2", _("Redialing network"))
o.depends('unattended_enable', '1');
o = s.option(form.Value, "autoreboot_system_uptime", _("System uptime greater than"))
o.rmempty = true
o.default = "24"
o.datatype = "uinteger"
o.description = _("Unit: hours")
o.depends('unattended_autoreboot_mode', '1');
o = s.option(form.Value, "autoreboot_network_uptime", _("Network uptime greater than"))
o.rmempty = true
o.default = "24"
o.datatype = "uinteger"
o.description = _("Unit: hours")
o.depends('unattended_autoreboot_mode', '2');
return m.render();
}
});
@@ -1,515 +0,0 @@
'use strict';
'require view';
'require fs';
'require ui';
'require poll';
'require uci';
return view.extend({
load: function () {
var self = this;
// 清除 localStorage 中的排序设置
localStorage.removeItem('sortColumn');
localStorage.removeItem('sortDirection');
uci.load('wechatpush');
return this.fetchAndRenderDevices().then(function () {
self.setupAutoRefresh();
});
},
fetchAndRenderDevices: function () {
var self = this;
return this.fetchDevices().then(function (data) {
var container = self.render(data);
self.switchContent(container);
}).catch(function (error) {
console.error('Error fetching or rendering devices:', error);
});
},
fetchDevices: function () {
var devices_path = '/tmp/wechatpush/devices.json';
return fs.read(devices_path).then(function (content) {
try {
var data = JSON.parse(content);
var wlanMap = {};
// 如果存在无线接口信息,解析为频段
if (data.wlan && Array.isArray(data.wlan)) {
data.wlan.forEach(function (wlan) {
wlanMap[wlan.interface] = wlan.band;
});
}
// 解析设备的接口信息
data.devices.forEach(function (device) {
if (device.type) {
device.interface = device.type;
} else if (wlanMap[device.interface]) {
device.interface = wlanMap[device.interface];
} else {
device.interface = "LAN";
}
});
return { devices: data.devices };
} catch (e) {
console.error('Error parsing JSON:', e);
return { devices: [] };
}
});
},
render: function (data) {
if (!data || !data.devices || !Array.isArray(data.devices)) {
return document.createElement('div');
}
var devices = data.devices.filter(device => device.status === 'online' || device.status === 'unknown');
var totalDevices = devices.length;
var headers = [_('Hostname'), _('IPv4 address'), _('MAC address'), _('Interfaces'), _('Connection Point'), _('Online time'), _('Details')];
var columns = ['name', 'ip', 'mac', 'interface', 'parent', 'uptime', 'usage'];
var visibleColumns = [];
var hasData = false;
// 获取配置中的默认排序列
var defaultSortColumn = uci.get('wechatpush', 'config', 'defaultSortColumn') || 'ip';
var defaultSortDirection = (defaultSortColumn === 'uptime') ? 'desc' : 'asc';
// 获取存储的排序设置,如果没有则使用默认设置
var storedSortColumn = localStorage.getItem('sortColumn');
var storedSortDirection = localStorage.getItem('sortDirection');
var currentSortColumn = storedSortColumn || defaultSortColumn;
var currentSortDirection = storedSortDirection || defaultSortDirection;
devices.sort(function (a, b) {
return compareDevices(a, b, currentSortColumn, currentSortDirection);
});
// 根据数据源决定可见列
for (var i = 0; i < columns.length; i++) {
var column = columns[i];
var hasColumnData = false;
// 特殊处理 parent 列
if (column === 'parent') {
var hasNonLocalParent = false;
for (var j = 0; j < devices.length; j++) {
var parentValue = devices[j][column];
if (parentValue && parentValue !== "Local") {
hasNonLocalParent = true;
break;
}
}
// 如果存在非 "Local" 的 parent 值,则显示该列
if (hasNonLocalParent) {
visibleColumns.push(i);
}
} else {
// 其他列的正常逻辑
for (var j = 0; j < devices.length; j++) {
if (devices[j][column] !== undefined && devices[j][column] !== '') {
hasColumnData = true;
hasData = true;
break;
}
}
if (hasColumnData) {
visibleColumns.push(i);
}
}
}
var style = `
/* 设备表格样式 */
.device-table {
width: 80%; /* 表格宽度占满父容器 */
border-collapse: collapse; /* 合并边框 */
margin-top: 10px; /* 顶部外边距 */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* 阴影效果 */
border-radius: 8px; /* 圆角边框 */
overflow: hidden; /* 内容溢出隐藏 */
}
.device-table th,
.device-table td {
padding: 10px; /* 单元格内边距 */
text-align: center; /* 文本居中 */
border: 1px solid #ddd; /* 边框样式 */
}
.device-table th {
background-color: rgba(0, 0, 0, 0.05); /* 表头背景色,透明 */
font-weight: bold; /* 粗体字体 */
cursor: pointer; /* 鼠标指针样式为指针 */
position: relative; /* 相对定位 */
}
.device-table th.sortable::after {
right: 10px; /* 右侧距离 */
top: 50%; /* 顶部偏移50% */
transform: translateY(-50%); /* 垂直居中 */
border-width: 5px 5px 0; /* 边框宽度 */
border-style: solid; /* 边框样式为实线 */
opacity: 0.6; /* 透明度 */
}
.device-table th.asc::after {
content: '';
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
border-width: 6px;
border-style: solid;
border-color: #666 transparent transparent transparent; /* 向上箭头颜色 */
}
.device-table th.desc::after {
content: '';
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
border-width: 6px;
border-style: solid;
border-color: transparent transparent #666 transparent; /* 向下箭头颜色 */
}
.device-table tbody tr:nth-child(even) {
background-color: rgba(0, 0, 0, 0.05); /* 偶数行背景色,透明 */
}
.device-table td:first-child {
text-align: left; /* 第一列文本左对齐 */
padding-left: 20px; /* 第一列左侧内边距 */
}
.device-table td a {
color: #007bff; /* 链接颜色 */
text-decoration: none; /* 去掉下划线 */
}
.device-table td a:hover {
text-decoration: underline; /* 鼠标悬停下划线 */
}
.device-table .hide {
display: none; /* 隐藏元素 */
}
@media (max-width: 767px) {
.device-table {
width: 100%; /* 表格宽度占满父容器 */
overflow: hidden; /* 内容溢出隐藏 */
}
.device-table th,
.device-table td {
padding: 3px; /* 单元格内边距 */
text-align: center; /* 文本居中 */
border: 0.35px solid #ddd; /* 边框样式 */
}
.device-table td:first-child {
max-width: 80px;
}
.device-table td:first-child {
text-align: left; /* 第一列文本左对齐 */
padding-left: 2px; /* 第一列左侧内边距 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis; /* 显示省略号 */
}
/* 隐藏特定列 */
.device-table th[data-column="parent"],
.device-table td[data-column="parent"] {
display: none;
}
/* 隐藏接口列的文本部分 */
.device-table td[data-column="interface"] span:not(.iface-icon) {
display: none;
}
/* 调整图标样式 */
.device-table td[data-column="interface"] .iface-icon {
margin-right: 0; /* 去掉图标右侧的间距 */
}
}
`;
function createTable() {
var table = document.createElement('table');
table.classList.add('device-table');
var thead = document.createElement('thead');
var tr = document.createElement('tr');
for (var i = 0; i < headers.length; i++) {
var th = document.createElement('th');
th.textContent = headers[i];
if (visibleColumns.includes(i)) {
th.classList.add('sortable');
th.dataset.column = columns[i];
if (columns[i] === currentSortColumn) {
th.classList.add(currentSortDirection === 'asc' ? 'asc' : 'desc');
}
} else {
th.classList.add('hide');
}
tr.appendChild(th);
}
thead.appendChild(tr);
table.appendChild(thead);
var tbody = document.createElement('tbody');
devices.forEach(function (device) {
var row = document.createElement('tr');
for (var i = 0; i < columns.length; i++) {
if (visibleColumns.includes(i)) {
var cell = document.createElement('td');
cell.dataset.column = columns[i];
if (columns[i] === 'uptime') {
cell.textContent = calculateUptime(device['uptime'], window.innerWidth <= 767);
} else if (columns[i] === 'ip' && device['http_access']) {
var link = document.createElement('a');
link.href = `${device['http_access']}://${device['ip']}`;
link.textContent = device['ip'];
link.target = '_blank';
cell.appendChild(link);
} else if (columns[i] === 'interface') {
var icon = document.createElement('span');
icon.classList.add('iface-icon');
if (device['interface'] === '2.4G') {
icon.innerHTML = '📶';
} else if (device['interface'] === '5G') {
icon.innerHTML = '🛜';
} else if (device['interface'] === 'WiFi') {
icon.innerHTML = '🛜';
}
var text = document.createElement('span');
text.textContent = device['interface'];
cell.appendChild(icon);
cell.appendChild(text);
} else if (columns[i] === 'parent') {
if (device['parent']) {
var parentDevice = devices.find(d => {
// 统一转换为大写比较
var deviceMac = (d.mac || '').toUpperCase();
var parentMac = (device['parent'] || '').toUpperCase();
return deviceMac === parentMac || d.ip === device['parent'];
});
if (parentDevice) {
cell.textContent = parentDevice.name || parentDevice.ip;
} else {
cell.textContent = device['parent'];
}
} else {
cell.textContent = '';
}
} else {
cell.textContent = device[columns[i]];
}
row.appendChild(cell);
}
}
tbody.appendChild(row);
});
table.appendChild(tbody);
return table;
}
function calculateUptime(uptime, simpleFormat = false) {
var startTimeStamp = parseInt(uptime);
var currentTimeStamp = Math.floor(Date.now() / 1000);
var uptimeInSeconds = currentTimeStamp - startTimeStamp;
var days = Math.floor(uptimeInSeconds / (3600 * 24));
var hours = Math.floor((uptimeInSeconds % (3600 * 24)) / 3600);
var minutes = Math.floor((uptimeInSeconds % 3600) / 60);
var seconds = uptimeInSeconds % 60;
if (simpleFormat) {
return days > 0 ? `${days}d ${hours}h` :
hours > 0 ? `${hours}h ${minutes}m` :
minutes > 0 ? `${minutes}m ${seconds}s` :
`${seconds}s`;
} else {
if (days > 0) {
return _('%dd %dh').replace(/%d/g, match =>
match === '%d' ? days : hours);
} else if (hours > 0) {
return _('%dh %dm').replace(/%d/g, match =>
match === '%d' ? hours : minutes);
} else if (minutes > 0) {
return _('%dm %ds').replace(/%d/g, match =>
match === '%d' ? minutes : seconds);
} else {
return _('%ds').replace('%d', seconds);
}
}
}
function compareDevices(a, b, column, direction) {
var value1 = getValueForSorting(a, column);
var value2 = getValueForSorting(b, column);
// 处理 name/mac 列的 "unknown" 优先级
if (column === 'name' || column === 'mac') {
const isUnknown1 = (value1 === "unknown");
const isUnknown2 = (value2 === "unknown");
if (isUnknown1 !== isUnknown2) {
return direction === 'asc'
// 升序时 unknown 排最后(视为最大值),降序时排最前
//? (isUnknown1 ? 1 : -1)
//: (isUnknown1 ? -1 : 1);
// 升序时 unknown 排最前(视为最小值)
? (isUnknown1 ? -1 : 1)
: (isUnknown1 ? 1 : -1);
}
}
// 处理 parent 列的优先级
if (column === 'parent') {
const aHasValue = a.parent ? 1 : 0;
const bHasValue = b.parent ? 1 : 0;
if (aHasValue !== bHasValue) {
return direction === 'desc'
? (bHasValue - aHasValue)
: (aHasValue - bHasValue);
}
value1 = a.parent || '';
value2 = b.parent || '';
}
// 通用比较逻辑
if (value1 < value2) {
return direction === 'asc' ? -1 : 1;
} else if (value1 > value2) {
return direction === 'asc' ? 1 : -1;
}
return 0;
}
var interfaceDisplayMap = {
'2.4G': '2.4G',
'5G': '5G',
'WiFi': 'WiFi'
};
// 排序
function getValueForSorting(device, column) {
if (column === 'uptime') {
return parseInt(device['uptime']);
} else if (column === 'ip') {
return ipToNumber(device['ip']);
} else if (column === 'interface') {
return interfaceDisplayMap[device['interface']] || 'LAN';
} else if (column === 'parent') {
// 使用 parent 列的实际显示值进行排序
if (device['parent']) {
var parentDevice = devices.find(d => {
var deviceMac = (d.mac || '').toUpperCase();
var parentMac = (device['parent'] || '').toUpperCase();
return deviceMac === parentMac || d.ip === device['parent'];
});
if (parentDevice) {
return parentDevice.name || parentDevice.ip;
} else {
return device['parent'];
}
} else {
return '';
}
}
return device[column];
}
function ipToNumber(ipAddress) {
var parts = ipAddress.split('.');
var number = 0;
for (var i = 0; i < parts.length; i++) {
number = number * 256 + parseInt(parts[i]);
}
return number;
}
var container = document.createElement('div');
refreshContainer();
container.addEventListener('click', function (event) {
if (event.target.tagName === 'TH' && event.target.parentNode.rowIndex === 0) {
var columnIndex = event.target.cellIndex;
var column = columns[columnIndex];
var direction = 'asc';
// 使在线时间第一次点击方向为倒序
if (column === 'uptime' || column === 'parent') {
if (currentSortColumn !== column) {
// 首次点击该列,默认方向为 desc
direction = 'desc';
} else {
// 切换方向
direction = currentSortDirection === 'desc' ? 'asc' : 'desc';
}
} else if (column === currentSortColumn) {
direction = currentSortDirection === 'asc' ? 'desc' : 'asc';
} else {
direction = 'asc';
}
sortTable(column, direction);
}
});
function refreshContainer() {
container.innerHTML = '';
container.appendChild(document.createElement('h2')).textContent = _('Currently %s devices online').replace('%s', totalDevices);
container.appendChild(createTable());
container.appendChild(document.createElement('style')).textContent = style;
}
function sortTable(column, direction) {
devices.sort(function (a, b) {
return compareDevices(a, b, column, direction);
});
currentSortColumn = column;
currentSortDirection = direction;
// 存储排序设置
localStorage.setItem('sortColumn', currentSortColumn);
localStorage.setItem('sortDirection', currentSortDirection);
refreshContainer();
}
return container;
},
setupAutoRefresh: function () {
var self = this;
poll.add(L.bind(function () {
self.fetchAndRenderDevices();
}));
},
switchContent: function (newContent) {
var existingContainer = document.querySelector('#view');
if (!existingContainer) {
console.error('Table container not found.');
return;
}
existingContainer.innerHTML = '';
existingContainer.appendChild(newContent);
},
handleSave: null,
handleSaveApply: null,
handleReset: null
});
@@ -1,795 +0,0 @@
'use strict';
'require view';
'require fs';
'require ui';
'require uci';
'require rpc';
'require form';
'require poll';
'require tools.widgets as widgets';
'require tools.firewall as fwtool';
var callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
});
function getServiceStatus() {
return L.resolveDefault(callServiceList('wechatpush'), {}).then(function (res) {
console.log(res);
var isRunning = false;
try {
isRunning = res['wechatpush']['instances']['instance1']['running'];
} catch (e) { }
return isRunning;
});
}
function renderStatus(isRunning) {
var spanTemp = '<em><span style="color:%s"><strong>%s %s</strong></span></em>';
var renderHTML;
if (isRunning) {
renderHTML = String.format(spanTemp, 'green', _('wechatpush'), _('RUNNING'));
} else {
renderHTML = String.format(spanTemp, 'red', _('wechatpush'), _('NOT RUNNING'));
}
return renderHTML;
}
var cbiRichListValue = form.ListValue.extend({
renderWidget: function (section_id, option_index, cfgvalue) {
var choices = this.transformChoices();
var widget = new ui.Dropdown((cfgvalue != null) ? cfgvalue : this.default, choices, {
id: this.cbid(section_id),
sort: this.keylist,
optional: true,
select_placeholder: this.select_placeholder || this.placeholder,
custom_placeholder: this.custom_placeholder || this.placeholder,
validate: L.bind(this.validate, this, section_id),
disabled: (this.readonly != null) ? this.readonly : this.map.readonly
});
return widget.render();
},
value: function (value, title, description) {
if (description) {
form.ListValue.prototype.value.call(this, value, E([], [
E('span', { 'class': 'hide-open' }, [title]),
E('div', { 'class': 'hide-close', 'style': 'min-width:25vw' }, [
E('strong', [title]),
E('br'),
E('span', { 'style': 'white-space:normal' }, description)
])
]));
}
else {
form.ListValue.prototype.value.call(this, value, title);
}
}
});
return view.extend({
callHostHints: rpc.declare({
object: 'luci-rpc',
method: 'getHostHints',
expect: { '': {} }
}),
load: function () {
return Promise.all([
this.callHostHints()
]);
},
render: function (data) {
if (fwtool.checkLegacySNAT())
return fwtool.renderMigration();
else
return this.renderForwards(data);
},
renderForwards: function (data) {
var hosts = data[0],
m, s, o,
programPath = '/usr/share/wechatpush/wechatpush';
m = new form.Map('wechatpush', _('WeChat push'), _('A tool that can push device messages from OpenWrt to a mobile phone via WeChat or Telegram.<br /><br />If you encounter any issues while using it, please submit them here:') + '<a href="https://github.com/tty228/luci-app-wechatpush" target="_blank">' + _('GitHub Project Address') + '</a>');
s = m.section(form.TypedSection);
s.anonymous = true;
s.render = function () {
var statusView = E('p', { id: 'service_status' }, _('Collecting data ...'));
poll.add(function () {
return L.resolveDefault(getServiceStatus()).then(function (res) {
statusView.innerHTML = renderStatus(res);
});
});
setTimeout(function () {
poll.start();
}, 100);
return E('div', { class: 'cbi-section', id: 'status_bar' }, [
statusView
]);
}
s = m.section(form.NamedSection, 'config', 'wechatpush', _(''));
s.tab('basic', _('Basic Settings'));
s.tab('content', _('Push Content'));
s.tab('ipset', _('Auto Ban'));
s.tab('crontab', _('Scheduled Push'));
s.tab('disturb', _('Do Not Disturb'));
s.addremove = false;
s.anonymous = true;
// 基本设置
o = s.taboption('basic', form.Flag, 'enable', _('Enabled'));
o = s.taboption('basic', cbiRichListValue, 'jsonpath', _('Push Mode'));
o.value('', _('Close'),
_('Do not use push notifications, only use other features.'));
o.value('/usr/share/wechatpush/api/serverchan.json', _('WeChat serverchan'),
_('Using serverchan API, simple configuration, supports multiple push methods'));
o.value('/usr/share/wechatpush/api/serverchan3.json', _('Serverchan3 APP'),
_('Using serverchan3 API, simple configuration, support push to ServerChan App'));
o.value('/usr/share/wechatpush/api/qywx_mpnews.json', _('WeChat Work Image Message'),
_('Using WeChat Work application message, more complex configuration, and starting from June 20, 2022, additional configuration for trusted IP is required. Trusted IP cannot be shared. This channel is no longer recommended.'));
o.value('/usr/share/wechatpush/api/qywx_markdown.json', _('WeChat Work Markdown Version'),
_('WeChat Work application message in plain text format, no need to click the title to view the content, same as above'));
o.value('/usr/share/wechatpush/api/wxpusher.json', _('wxpusher'),
_('Another channel for WeChat push, the configuration is relatively simple, and only supports official accounts'));
o.value('/usr/share/wechatpush/api/pushplus.json', _('pushplus'),
_('Another channel for WeChat push, the configuration is relatively simple, and it supports multiple push methods'));
o.value('/usr/share/wechatpush/api/wpush.json', _('WPUSH'),
_('Multi-channel push via WPUSH (WeChat, App, Feishu, DingTalk, and more). Success when response code===0'));
o.value('/usr/share/wechatpush/api/telegram.json', _('Telegram'),
_('Telegram Bot Push'));
o.value('/usr/share/wechatpush/api/msmtp.json', _('msmtp'),
_('To send emails using msmtp, you can manually install msmtp and configure `/etc/msmtprc`, or configure the SMTP service here to override the default msmtp configuration file.'));
o.value('/usr/share/wechatpush/api/diy.json', _('Custom Push'),
_('By modifying the JSON file, you can use a custom API'));
o = s.taboption('basic', form.Value, 'sckey', _('「wechatpush」sendkey'));
o.description = _('Get Instructions') + ' <a href="https://sct.ftqq.com/r/13285" target="_blank">' + _('Click here') + '</a>';
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/serverchan.json');
o = s.taboption('basic', form.Value, 'sc3key', _('「Serverchan3」sendkey'));
o.description = _('Get Instructions') + ' <a href="https://sc3.ft07.com/" target="_blank">' + _('Click here') + '</a>';
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/serverchan3.json');
o = s.taboption('basic', form.Value, 'sc3uid', _('「Serverchan3」uid'));
o.description = _('ServerChan3 uid');
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/serverchan3.json');
o = s.taboption('basic', form.Value, 'sc3tags', _('「Serverchan3」tags'));
o.description = _('ServerChan3 message tags, split with |');
o.depends('jsonpath', '/usr/share/wechatpush/api/serverchan3.json');
o = s.taboption('basic', form.Value, 'corpid', _('corpid'));
o.description = _('Get Instructions') + ' <a href="https://work.weixin.qq.com/api/doc/10013" target="_blank">' + _('Click here') + '</a>';
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_mpnews.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_markdown.json');
o = s.taboption('basic', form.Value, 'userid', _('userid'));
o.rmempty = false;
o.description = _('Send to All App Users, enter @all');
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_mpnews.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_markdown.json');
o = s.taboption('basic', form.Value, 'agentid', _('agentid'));
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_mpnews.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_markdown.json');
o = s.taboption('basic', form.Value, 'corpsecret', _('Secret'));
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_mpnews.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_markdown.json');
o = s.taboption('basic', form.Value, 'mediapath', _('Thumbnail Image File Path'))
o.rmempty = false;
o.default = '/usr/share/wechatpush/api/logo.jpg';
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_mpnews.json');
o.description = _('Supports JPG and PNG formats within 2MB <br> Optimal size: 900383 or 2.35:1');
o = s.taboption('basic', form.Value, 'proxy_ip', _('Trusted IP address'));
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_mpnews.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_markdown.json');
o.description = _('If the application was created after June 20, 2022, you need to set the trusted IP. This option should be used in conjunction with a proxy server.');
o = s.taboption('basic', form.Value, 'wxpusher_apptoken', _('appToken'));
o.description = _('Get Instructions') + ' <a href="https://wxpusher.zjiecode.com/docs/#/?id=%e5%bf%ab%e9%80%9f%e6%8e%a5%e5%85%a5" target="_blank">' + _('Click here') + '</a>';
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/wxpusher.json');
o = s.taboption('basic', form.Value, 'wxpusher_uids', _('uids'));
o.depends('jsonpath', '/usr/share/wechatpush/api/wxpusher.json');
o = s.taboption('basic', form.Value, 'wxpusher_topicIds', _('topicIds(Mass sending)'));
o.description = _('Get Instructions') + ' <a href="https://wxpusher.zjiecode.com/docs/#/?id=%e5%8f%91%e9%80%81%e6%b6%88%e6%81%af-1" target="_blank">' + _('Click here') + '</a>';
o.depends('jsonpath', '/usr/share/wechatpush/api/wxpusher.json');
o = s.taboption('basic', form.Value, 'pushplus_token', _('pushplus_token'));
o.description = _('Get Instructions') + ' <a href="http://www.pushplus.plus/" target="_blank">' + _('Click here') + '</a>';
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/pushplus.json');
o = s.taboption('basic', form.Value, 'wpush_apikey', _('WPUSH apikey'));
o.description = _('Get Instructions') + ' <a href="https://wpush.cn/" target="_blank">' + _('Click here') + '</a>' + _('<br />API docs: https://docs.wpush.cn/docs/api/message.html — success when code===0');
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/wpush.json');
o = s.taboption('basic', form.Value, 'tg_token', _('Bot Token'));
o.description = _('Get Bot') + ' <a href="https://t.me/BotFather" target="_blank">' + _('Click here') + '</a>' + _('<br />Send a message to the created bot to initiate a conversation.');
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/telegram.json');
o = s.taboption('basic', form.Value, 'tg_chat_id', _('Chat ID'));
o.description = _('Get chat_id') + ' <a href="https://t.me/getuserIDbot" target="_blank">' + _('Click here') + '</a>' + _('<br />If you want to send to a group/channel, please create a non-Chinese group/channel (for easier chatid lookup, you can rename it later).<br />Add the bot to the group, send a message, and use https://api.telegram.org/bot token /getUpdates to obtain the chatid.');
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/telegram.json');
o = s.taboption('basic', form.Value, 'tg_thread_id', _('Message Thread ID'));
o.description = _('Optional: specify a thread (topic) ID for forum-style Telegram chats.');
o.rmempty = false;
o.optional = true;
o.depends('jsonpath', '/usr/share/wechatpush/api/telegram.json');
o = s.taboption('basic', form.Value, 'tg_api_server', _('API Server URL'));
o.placeholder = 'https://api.telegram.org';
o.rmempty = false;
o.depends('jsonpath', '/usr/share/wechatpush/api/telegram.json');
o = s.taboption('basic', form.Value, 'recipient_email', _('Recipient Email Address'));
o.depends('jsonpath', '/usr/share/wechatpush/api/msmtp.json');
o.rmempty = false;
o.description = _('opkg update<br />opkg install msmtp');
o = s.taboption('basic', form.Value, 'smtp_host', _('SMTP Host'));
o.depends('jsonpath', '/usr/share/wechatpush/api/msmtp.json');
o.placeholder = "mail.oursite.example";
o = s.taboption('basic', form.Value, 'smtp_port', _('SMTP Port'));
o.depends('jsonpath', '/usr/share/wechatpush/api/msmtp.json');
o.placeholder = "25";
o = s.taboption('basic', form.ListValue, 'smtp_tls', _('Enable/Disable TLS'));
o.depends('jsonpath', '/usr/share/wechatpush/api/msmtp.json');
o.widget = 'radio';
o.value('on', _('Enable'));
o.value('off', _('Disable'));
o.value('', _('Ignore'));
o.default = '';
o = s.taboption('basic', form.ListValue, 'smtp_starttls', _('Enable/Disable STARTTLS'));
o.depends('jsonpath', '/usr/share/wechatpush/api/msmtp.json');
o.widget = 'radio';
o.value('on', _('Enable'));
o.value('off', _('Disable'));
o.value('', _('Ignore'));
o.default = '';
o = s.taboption('basic', form.Value, 'smtp_user', _('User'));
o.depends('jsonpath', '/usr/share/wechatpush/api/msmtp.json');
o = s.taboption('basic', form.Value, 'smtp_passwordeval', _('Password Command'));
o.depends('jsonpath', '/usr/share/wechatpush/api/msmtp.json');
o.description = _('Set the password for authentication to the output (stdout) of the command.');
o = s.taboption('basic', form.Value, 'smtp_from', _('From Address'));
o.depends('jsonpath', '/usr/share/wechatpush/api/msmtp.json');
o.placeholder = "user@oursite.example";
o = s.taboption('basic', form.Value, 'smtp_from_full_name', _('From Full Name'));
o.depends('jsonpath', '/usr/share/wechatpush/api/msmtp.json');
o.description = _('If set, it is usually displayed as a nickname of the sender in email clients.');
o = s.taboption('basic', form.TextValue, 'diy_json', _('Custom Push'));
o.rows = 28;
o.wrap = 'oft';
o.cfgvalue = function (section_id) {
return fs.trimmed('/usr/share/wechatpush/api/diy.json');
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/usr/share/wechatpush/api/diy.json', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
o.description = _('Please refer to the comments and other interface files for modifications. Limited resources, no longer supporting more interfaces, please debug on your own.<br />You can use a similar website to check the JSON file format: https://www.google.com/search?q=JSON+Parser+Online<br />Please use the 「Save」 button in the text box.');
o.depends('jsonpath', '/usr/share/wechatpush/api/diy.json');
o = s.taboption('basic', form.Value, 'proxy_address', _('Proxy Address'));
o.description = _('When you want to use a proxy to push information, you can use this option.<br/>This may be helpful for scenarios like trusted IPs for WeChat Work.Using special characters may cause sending failure.<br/>Example:<br/>http://username:password@127.0.0.1:1080<br/>socks5://username:password@127.0.0.1:1080');
o.depends('jsonpath', '/usr/share/wechatpush/api/serverchan.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/serverchan3.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_mpnews.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/wxpusher.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/pushplus.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/wpush.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/telegram.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/diy.json');
o = s.taboption('basic', form.Button, '_test', _('Send Test'), _('You may need to save the configuration before sending.'));
o.inputstyle = 'add';
o.onclick = function () {
var _this = this;
return fs.exec(programPath, ['test']).then(function (res) {
if (res.code === 0)
_this.description = _('Message sent successfully. If you don\'t receive the message, please check the logs for manual processing.');
else if (res.code === 1)
_this.description = _('Sending failed');
return _this.map.reset();
}).catch(function (err) {
ui.addNotification(null, E('p', [_('Unknown error: %s.').format(err)]));
_this.description = _('Sending failed');
return _this.map.reset();
});
}
o.depends('jsonpath', '/usr/share/wechatpush/api/serverchan.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/serverchan3.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/qywx_mpnews.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/wxpusher.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/pushplus.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/wpush.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/telegram.json');
o.depends('jsonpath', '/usr/share/wechatpush/api/diy.json');
o = s.taboption('basic', form.Value, 'device_name', _('Device Name'));
o.description = _('The device name will be displayed in the push message title to identify the source device of the message.');
o = s.taboption('basic', form.Value, 'sleeptime', _('Check Interval (s)'));
o.rmempty = false;
o.placeholder = '60';
o.datatype = 'and(uinteger,min(10))';
o.description = _('Shorter intervals provide quicker response but consume more system resources.');
o = s.taboption('basic', cbiRichListValue, 'oui_data', _('MAC Device Database'));
o.value('', _('Close'),
_('Do not use MAC device database'));
o.value('1', _('Simplified Version'),
_('Includes common device manufacturers, occupies approximately 200Kb of space'));
o.value('2', _('Full Version'),
_('Download the complete database, processed size is approximately 1.3Mb'));
o.value('3', _('Network Query'),
_('Enables network query, slower response. Only use if space is limited.'));
o = s.taboption('basic', form.Button, '_update_oui', _('Update MAC Device Database'));
o.inputstyle = 'add';
o.onclick = function () {
var _this = this;
return fs.exec('/usr/libexec/wechatpush-call', ['down_oui']).then(function (res) {
if (res.code === 2) {
_this.description = _('Database is up to date, skipping update');
}
return _this.map.reset();
}).catch(function (err) {
ui.addNotification(null, E('p', [_('Unknown error: %s.').format(err)]));
_this.description = _('Browser timeout or unknown error. If the log shows that the update process has been established, please ignore this error: %s.');
return _this.map.reset();
});
};
o.depends('oui_data', '1');
o.depends('oui_data', '2');
o = s.taboption('basic', form.Flag, 'reset_regularly', _('Reset Traffic Data Every Day at Midnight'));
o = s.taboption('basic', form.Flag, 'prefer_client_page', _('Prefer displaying online devices'));
o.default = '1';
o.description = _('Only effective when service is running');
o = s.taboption('basic', form.Flag, 'debuglevel', _('Enable Logging'));
o = s.taboption('basic', form.TextValue, '_device_aliases', _('Device Alias'));
o.rows = 20;
o.wrap = 'oft';
o.cfgvalue = function (section_id) {
return fs.trimmed('/usr/share/wechatpush/api/device_aliases.list');
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/usr/share/wechatpush/api/device_aliases.list', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
o.description = _('Please enter the device MAC and device alias separated by a space, such as:<br/> XX:XX:XX:XX:XX:XX My Phone<br/> 192.168.1.2 My PC<br />Please use the 「Save」 button in the text box.');
// 推送内容
o = s.taboption('content', form.MultiValue, 'lite_enable', _('Simplified mode'));
o.value('device', _('Simplify the current device list'));
o.value('nowtime', _('Simplify the current time'));
o.value('content', _('Push only the title'));
o = s.taboption('content', form.Button, '_getip', _('Test IP acquisition command'), _('You may need to save the configuration before sending.'));
o.inputstyle = 'action';
o.onclick = function () {
var _this = this;
return fs.exec(programPath, ['getip']).then(function (res) {
if (!res.stdout) {
throw new Error(_('Returned value is empty'));
}
_this.description = res.stdout.trim();
return _this.map.reset();
}).catch(function (err) {
_this.description = _('Fetch failed:') + err.message;
return _this.map.reset();
});
};
o = s.taboption('content', cbiRichListValue, 'get_ipv4_mode', _('IPv4 Dynamic Notification'));
o.value('', _('Close'),
_(' '));
o.value('1', _('Obtain through interface'),
_(' '));
o.value('2', _('Obtain through URL'),
_('May fail due to server stability and frequent connections.<br/>If the interface can obtain the IP address properly, it is not recommended to use this method.'));
o = s.taboption('content', widgets.DeviceSelect, 'ipv4_interface', _("Device"));
o.description = _('Generally, it should be the pppoe-wan or WAN interface. For multi-dial environments, please select the appropriate interface yourself.');
o.modalonly = true;
o.multiple = false;
o.default = 'pppoe-wan';
o.depends('get_ipv4_mode', '1');
o = s.taboption('content', form.TextValue, 'ipv4_list', _('IPv4 API List'));
o.depends('get_ipv4_mode', '2');
o.optional = false;
o.rows = 8;
o.wrap = 'oft';
o.cfgvalue = function (section_id) {
return fs.trimmed('/usr/share/wechatpush/api/ipv4.list');
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/usr/share/wechatpush/api/ipv4.list', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
o.description = _('Access a random address from the list above,URLs in the list are specific to Chinese websites. If you need to use this feature, please replace the URLs with the ones available to you.<br/>Please use the 「Save」 button in the text box.');
o = s.taboption('content', cbiRichListValue, 'get_ipv6_mode', _('IPv6 Dynamic Notification'));
o.value('', _('Close'),
_(' '));
o.value('1', _('Obtain through interface'),
_(' '));
o.value('2', _('Obtain through URL'),
_('May fail due to server stability and frequent connections.<br/>If the interface can obtain the IP address properly, it is not recommended to use this method.'));
o = s.taboption('content', widgets.DeviceSelect, 'ipv6_interface', _("Device"));
o.description = _('Generally, it should be the pppoe-wan or WAN interface. For multi-dial environments, please select the appropriate interface yourself.');
o.modalonly = true;
o.multiple = false;
o.default = 'WAN';
o.depends('get_ipv6_mode', '1');
o = s.taboption('content', form.TextValue, 'ipv6_list', _('IPv6 API List'));
o.depends('get_ipv6_mode', '2')
o.optional = false;
o.rows = 8;
o.wrap = 'oft';
o.cfgvalue = function (section_id) {
return fs.trimmed('/usr/share/wechatpush/api/ipv6.list');
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/usr/share/wechatpush/api/ipv6.list', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
o.description = _('Access a random address from the list above,URLs in the list are specific to Chinese websites. If you need to use this feature, please replace the URLs with the ones available to you.<br/>Please use the 「Save」 button in the text box.');
o = s.taboption('content', form.MultiValue, 'device_notification', _('Device Online/Offline Notification'));
o.value('online', _('Online Notification'));
o.value('offline', _('Offline Notification'));
o.modalonly = true;
o = s.taboption('content', form.MultiValue, 'cpu_notification', _('CPU Alert'));
o.value('load', _('Load Alert'));
o.value('temp', _('Temperature Alert'));
o.modalonly = true;
o.description = _('Device alert will be triggered only if it exceeds the set value continuously for five minutes, and there won\'t be a second reminder within an hour.');
o = s.taboption('content', form.Value, 'cpu_load_threshold', _('Load alert threshold'));
o.rmempty = false;
o.placeholder = '2';
o.depends({ cpu_notification: "load", '!contains': true });
o.validate = function (section_id, value) {
var floatValue = parseFloat(value);
if (!isNaN(floatValue) && floatValue.toString() === value) {
return true;
}
return _('Please enter a numeric value only');
};
o.description = _('In general, when the load value is lower than the number of logical cores, you typically don\'t need to pay much attention to it.');
o = s.taboption('content', form.Value, 'temperature_threshold', _('Temperature alert threshold'));
o.rmempty = false;
o.placeholder = '80';
o.datatype = 'and(uinteger,min(1))';
o.depends({ cpu_notification: "temp", '!contains': true });
o.description = _('Please confirm that the device can retrieve temperature. If you need to modify the command, please go to advanced settings.');
o = s.taboption('content', form.MultiValue, 'login_notification', _('Login Notification'));
o.value('web_logged', _('Web Login'));
o.value('ssh_logged', _('SSH Login'));
o.value('web_login_failed', _('Frequent Web Login Errors'));
o.value('ssh_login_failed', _('Frequent SSH Login Errors'));
o.modalonly = true;
o = s.taboption('content', form.Value, 'login_max_num', _('Login failure count'));
o.default = '3';
o.rmempty = false;
o.datatype = 'and(uinteger,min(1))';
o.depends({ login_notification: "web_login_failed", '!contains': true });
o.depends({ login_notification: "ssh_login_failed", '!contains': true });
o.description = _('Send notification after exceeding the count, and optionally auto-ban IP');
o = s.taboption('content', form.Flag, 'client_usage', _('Device abnormal traffic alert'));
o.description = _('Please ensure that you can retrieve device traffic information correctly, otherwise this feature will not work properly');
o.default = '0';
o = s.taboption('content', form.Value, 'client_usage_max', _('Per-minute traffic limit'));
o.placeholder = '10M';
o.rmempty = false;
o.depends('client_usage', '1');
o.description = _('Abnormal traffic alert (byte), you can append K or M');
o = s.taboption('content', form.Flag, 'client_usage_disturb', _('Abnormal traffic do not disturb'));
o.default = '0';
o.depends('client_usage', '1');
o = fwtool.addMACOption(s, 'content', 'client_usage_whitelist', _('Abnormal traffic monitoring list'),
_('Please select device MAC'), hosts);
o.rmempty = true;
o.datatype = 'list(neg(macaddr))';
o.depends('client_usage_disturb', '1');
// 自动封禁
o = s.taboption('ipset', form.Flag, 'login_web_black', _('Auto-ban unauthorized login devices'));
o.default = '0';
o.depends({ login_notification: "web_login_failed", '!contains': true });
o.depends({ login_notification: "ssh_login_failed", '!contains': true });
o = s.taboption('ipset', form.Value, 'login_ip_black_timeout', _('Blacklisting time (s)'));
o.default = '86400';
o.rmempty = false;
o.datatype = 'and(uinteger,min(0))';
o.depends('login_web_black', '1');
o.description = _('\"0\" in ipset means permanent blacklist, use with caution. If misconfigured, change the device IP and clear rules in LUCI.<br/>Note: The whitelist for bans is located under the \"Do Not Disturb\" tab.');
o = s.taboption('ipset', form.Flag, 'port_knocking_enable', _('Port knocking'));
o.default = '0';
o.description = _('If you have disabled LAN port inbound and forwarding in Firewall - Zone Settings, it won\'t work.');
o.depends({ login_notification: "web_login_failed", '!contains': true });
o.depends({ login_notification: "ssh_login_failed", '!contains': true });
o = s.taboption('ipset', form.Value, 'login_port_white', _('Port'));
o.default = '';
o.description = _('Open port after successful login<br/>example:\"22\"、\"21:25\"、\"21:25,135:139\"');
o.depends('port_knocking_enable', '1');
o = s.taboption('ipset', form.DynamicList, 'login_port_forward_list', _('Port Forwards'));
o.default = '';
o.description = _('Example: Forward port 13389 of this device (IPv4:10.0.0.1 / IPv6:fe80::10:0:0:2) to port 3389 of (IPv4:10.0.0.2 / IPv6:fe80::10:0:0:8)<br/>\"10.0.0.1,13389,10.0.0.2,3389\"<br/>\"fe80::10:0:0:1,13389,fe80::10:0:0:2,3389\"');
o.depends('port_knocking_enable', '1');
o = s.taboption('ipset', form.Value, 'login_ip_white_timeout', _('Release time (s)'));
o.default = '86400';
o.datatype = 'and(uinteger,min(0))';
o.description = _('\"0\" in ipset means permanent release, use with caution');
o.depends('port_knocking_enable', '1');
o = s.taboption('ipset', form.TextValue, 'ip_black_list', _('IP blacklist'));
o.rows = 8;
o.wrap = 'soft';
o.cfgvalue = function (section_id) {
return fs.trimmed('/usr/share/wechatpush/api/ip_blacklist');
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/usr/share/wechatpush/api/ip_blacklist', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
o.depends('login_web_black', '1');
o.description = _('You can add or delete here, the numbers after represent the remaining time. When adding, only the IP needs to be entered.<br/>Due to limitations on the web interface, please keep one empty line if you need to clear the content; otherwise, it will not be possible to submit. ╮(╯_╰)╭<br/>Please use the 「Save」 button in the text box.');
// 定时推送
o = s.taboption('crontab', cbiRichListValue, 'crontab_mode', _('Scheduled Tasks'));
o.value('', _('Close'),
_(' '));
o.value('1', _('Scheduled sending'),
_('Send at the same time every day'));
o.value('2', _('Interval sending'),
_('Starting from 00:00, send every * hours'));
o = s.taboption('crontab', form.MultiValue, 'crontab_regular_time', _('Sending time'));
for (var t = 0; t <= 23; t++) {
o.value(t, _('Every day') + t + _('clock'));
}
o.modalonly = true;
o.depends("crontab_mode", "1");
o = s.taboption('crontab', form.ListValue, 'crontab_interval_time', _('Interval sending'));
o.default = "6"
for (var t = 0; t <= 12; t++) {
o.value(t, _("") + t + _("Hour"));
}
o.default = '';
o.datatype = "uinteger";
o.depends('crontab_mode', '2');
o.description = _('Starting from 00:00, send every * hours');
o = s.taboption('crontab', form.Value, 'send_title', _('Push title'));
o.depends('crontab_mode', '1');
o.depends('crontab_mode', '2');
o.placeholder = _('OpenWrt Router Status:');
o.description = _('Using special characters may cause sending failure');
o = s.taboption('crontab', form.MultiValue, 'send_notification', _('Push content'));
o.value('router_status', _('System running status'));
o.value('router_temp', _('Device temperature'));
o.value('wan_info', _('WAN info'));
o.value('client_list', _('Client list'));
o.modalonly = true;
o.depends('crontab_mode', '1');
o.depends('crontab_mode', '2');
o = s.taboption('crontab', form.Button, '_send', _('Manual sending'), _('You may need to save the configuration before sending.<br/>Due to browser timeout limitations, if the program is not running, it may exit due to timeout during device list initialization'));
o.inputstyle = 'add';
o.onclick = function () {
var _this = this;
return fs.exec(programPath, ['send']).then(function (res) {
if (res.code === 0)
_this.description = _('Message sent successfully. If you don\'t receive the message, please check the logs for manual processing.');
else if (res.code === 1)
_this.description = _('Sending failed');
return _this.map.reset();
}).catch(function (err) {
ui.addNotification(null, E('p', [_('Unknown error: %s.').format(err)]));
_this.description = _('Sending failed');
return _this.map.reset();
});
}
// 免打扰
o = s.taboption('disturb', cbiRichListValue, 'do_not_disturb_mode', _('Do Not Disturb time setting'));
o.value('', _('Close'),
_(' '));
o.value('1', _('Mode 1: Script Suspension'),
_('Suspend all script actions, including unattended tasks, until the end of the time period'));
o.value('2', _('Mode 2: Silent Mode'),
_('Stop sending notifications, but log normally'));
o = s.taboption('disturb', form.ListValue, 'do_not_disturb_starttime', _('Do Not Disturb start time'));
for (var t = 0; t <= 23; t++) {
o.value(t, _('Every day') + t + _('clock'));
}
o.default = '0';
o.datatype = "uinteger"
o.depends('do_not_disturb_mode', '1');
o.depends('do_not_disturb_mode', '2');
o = s.taboption('disturb', form.ListValue, 'do_not_disturb_endtime', _('Do Not Disturb end time'));
for (var t = 0; t <= 23; t++) {
o.value(t, _('Every day') + t + _('clock'));
}
o.default = 8
o.datatype = "uinteger"
o.depends('do_not_disturb_mode', '1');
o.depends('do_not_disturb_mode', '2');
o = s.taboption('disturb', cbiRichListValue, 'mac_filtering_mode_1', _('MAC Filtering Mode 1'));
o.value('', _('Close'),
_(' '));
o.value('allow', _('Ignore devices in the list'),
_('Ignored devices will not receive notifications or be logged'));
o.value('block', _('Notify only devices in the list'),
_('Ignored devices will not receive notifications or be logged'));
o.value('interface', _('Notify only devices using this interface'),
_('Multiple selections are not supported at the moment'));
o = fwtool.addMACOption(s, 'disturb', 'up_down_push_whitelist', _('Ignored device list'),
_('Please select device MAC'), hosts);
o.datatype = 'list(neg(macaddr))';
o.depends('mac_filtering_mode_1', 'allow');
o = fwtool.addMACOption(s, 'disturb', 'up_down_push_blacklist', _('Followed device list'),
_('Please select device MAC'), hosts);
o.datatype = 'list(neg(macaddr))';
o.depends('mac_filtering_mode_1', 'block');
o.description = _('AA:AA:AA:AA:AA:AA\\|BB:BB:BB:BB:BB:BB Multiple MAC addresses can be treated as the same user.<br/>Notifications will not be sent once any device is online, and notifications will only be sent when all devices are offline to avoid frequent notifications in dual Wi-Fi scenarios.'); // 有点问题,待修复
o = s.taboption('disturb', widgets.DeviceSelect, 'up_down_push_interface', _("Device"));
o.description = _('Notify only devices using this interface');
o.modalonly = true;
o.multiple = false;
o.depends('mac_filtering_mode_1', 'interface');
o = s.taboption('disturb', cbiRichListValue, 'mac_filtering_mode_2', _('MAC Filtering Mode 2'));
o.value('', _('Close'),
_(' '));
o.value('mac_online', _('Do Not Disturb when devices are online'),
_('No notifications will be sent when any device in the list is online'));
o.value('mac_offline', _('Do Not Disturb when devices are offline'),
_('No notifications will be sent when any device in the list is offline'));
o = fwtool.addMACOption(s, 'disturb', 'mac_online_list', _('Do Not Disturb device online list'),
_('Please select device MAC'), hosts);
o.datatype = 'list(neg(macaddr))';
o.depends('mac_filtering_mode_2', 'mac_online');
o = fwtool.addMACOption(s, 'disturb', 'mac_offline_list', _('Do Not Disturb device offline list'),
_('Please select device MAC'), hosts);
o.datatype = 'list(neg(macaddr))';
o.depends('mac_filtering_mode_2', 'mac_offline');
o = s.taboption('disturb', form.Value, 'cpu_threshold_duration', _('When CPU temperature or load exceeds the threshold continuously for (s) seconds.'));
o.rmempty = false;
o.default = '300';
o.datatype = 'and(uinteger)';
o.description = _('If set to 0, it\'s a single check without considering duration.');
o.depends({ cpu_notification: "temp", '!contains': true });
o.depends({ cpu_notification: "load", '!contains': true });
o = s.taboption('disturb', form.Value, 'cpu_notification_delay', _('CPU alarm quiet time (seconds)'));
o.rmempty = false;
o.default = '3600';
o.datatype = 'and(uinteger)';
o.description = _('No repeat notifications within the set time after the initial push notification.');
o.depends({ cpu_notification: "temp", '!contains': true });
o.depends({ cpu_notification: "load", '!contains': true });
o = s.taboption('disturb', cbiRichListValue, 'login_disturb', _('Do Not Disturb for Login Reminders'));
o.value('', _('Close'),
_(' '));
o.value('1', _('Only record in the log'),
_('Ignore all login notifications (including failed logins), and only record them in the log.'));
o.value('2', _('Send notification only on the first login'),
_('Send notification only once within the specified time interval.'));
o = fwtool.addIPOption(s, 'disturb', 'login_ip_white_list', _('Login Alert (Auto-Ban) Whitelist'), null, 'ipv4', hosts, true);
o.datatype = 'ipaddr';
o.depends({ login_notification: "web_logged", '!contains': true });
o.depends({ login_notification: "ssh_logged", '!contains': true });
o.depends({ login_notification: "web_login_failed", '!contains': true });
o.depends({ login_notification: "ssh_login_failed", '!contains': true });
o.description = _('Add the IP addresses in the list to the whitelist for the blocking function (if available), and ignore automatic blocking and login event notifications. Only record in the log. Mask notation is currently not supported.');
o = s.taboption('disturb', form.Flag, 'login_log_enable', _('Login reminder log anti-flooding'));
o.description = _('Users in the whitelist or during the undisturbed time period after their first login IP will be exempt from log recording, preventing log flooding.');
o.depends('login_disturb', '1');
o.depends('login_disturb', '2');
o = s.taboption('disturb', form.Value, 'login_notification_delay', _('Login reminder do not disturb time (s)'));
o.rmempty = false;
o.placeholder = '3600';
o.datatype = 'and(uinteger,min(10))';
o.description = _('Send notification after the first login and do not repeat within the specified time<br/>Take a shortcut and read the login time from the log');
o.depends('login_disturb', '2');
return m.render();
}
});
@@ -1,110 +0,0 @@
'use strict';
'require dom';
'require fs';
'require poll';
'require uci';
'require view';
'require form';
return view.extend({
render: function () {
var css = `
/* 日志框文本区域 */
#log_textarea {
margin-top: 10px;
}
#log_textarea pre {
padding: 10px; /* 内边距 */
border: 1px solid #ddd; /* 边框颜色 */
border-radius: 6px; /* 边框圆角 */
font-family: Consolas, Menlo, Monaco, monospace;
font-size: 14px;
line-height: 1.6; /* 行高 */
white-space: pre-wrap;
word-wrap: break-word;
overflow-y: auto;
max-height: 600px;
}
/* 5s 自动刷新文字 */
.cbi-section small {
margin-left: 5px;
font-size: 12px;
color: #666; /* 深灰色文字 */
}
`;
var log_textarea = E('div', { 'id': 'log_textarea' },
E('img', {
'src': L.resource(['icons/loading.gif']),
'alt': _('Loading...'),
'style': 'vertical-align:middle'
}, _('Collecting data ...'))
);
var log_path = '/tmp/wechatpush/wechatpush.log';
var lastLogContent = '';
var clear_log_button = E('div', {}, [
E('button', {
'class': 'cbi-button cbi-button-remove',
'click': function (ev) {
ev.preventDefault();
var button = ev.target;
button.disabled = true;
button.textContent = _('Clear Logs...');
fs.exec_direct('/usr/libexec/wechatpush-call', ['clear_log'])
.then(function () {
button.textContent = _('Logs cleared successfully!');
button.disabled = false;
button.textContent = _('Clear Logs');
// 立即刷新日志显示框
var log = E('pre', { 'wrap': 'pre' }, [_('Log is clean.')]);
dom.content(log_textarea, log);
lastLogContent = '';
})
.catch(function () {
button.textContent = _('Failed to clear log.');
button.disabled = false;
button.textContent = _('Clear Logs');
});
}
}, _('Clear Logs'))
]);
poll.add(L.bind(function () {
return fs.read_direct(log_path, 'text')
.then(function (res) {
var newContent = res.trim() || _('Log is clean.');
if (newContent !== lastLogContent) {
var log = E('pre', { 'wrap': 'pre' }, [newContent]);
dom.content(log_textarea, log);
log.scrollTop = log.scrollHeight;
lastLogContent = newContent;
}
}).catch(function (err) {
var log;
if (err.toString().includes('NotFoundError')) {
log = E('pre', { 'wrap': 'pre' }, [_('Log file does not exist.')]);
} else {
log = E('pre', { 'wrap': 'pre' }, [_('Unknown error: %s').format(err)]);
}
dom.content(log_textarea, log);
});
}));
return E('div', { 'class': 'cbi-map' }, [
E('style', [css]),
E('div', { 'class': 'cbi-section' }, [
clear_log_button,
log_textarea,
E('small', {}, _('Refresh every 5 seconds.').format(L.env.pollinterval)),
E('div', { 'class': 'cbi-section-actions cbi-section-actions-right' })
])
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});
@@ -1,48 +0,0 @@
'use strict';
'require view';
'require rpc';
'require uci';
var callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
});
function getServiceStatus() {
return L.resolveDefault(callServiceList('wechatpush'), {}).then(function (res) {
console.log(res);
var isRunning = false;
try {
isRunning = res['wechatpush']['instances']['instance1']['running'];
} catch (e) { }
return isRunning;
});
}
return view.extend({
load: function () {
return Promise.all([
getServiceStatus(),
uci.load('wechatpush')
]);
},
render: function (data) {
var status = data[0];
// 检查配置,默认为 '1' (开启)
var preferClient = uci.get('wechatpush', 'config', 'prefer_client_page') !== '0';
// 如果服务正在运行 且 优先展示在线设备开关开启,跳转到 client 页面
if (status && preferClient) {
window.location.replace('/cgi-bin/luci/admin/services/wechatpush/client');
} else {
window.location.replace('/cgi-bin/luci/admin/services/wechatpush/config');
}
},
handleSave: null,
handleSaveApply: null,
handleReset: null
});
File diff suppressed because it is too large. Load diff
@@ -1,11 +0,0 @@
config wechatpush "config"
option enable '0'
option sleeptime '60'
option debuglevel '1'
option up_timeout '2'
option down_timeout '10'
option timeout_retry_count '2'
option thread_num '3'
option tg_api_server 'https://api.telegram.org'
option tg_chat_id ''
@@ -1,38 +0,0 @@
#!/bin/sh /etc/rc.common
START=99
STOP=90
USE_PROCD=1
dir="/tmp/wechatpush/"
start_service() {
procd_open_instance
enable_value=$(uci get wechatpush.config.enable 2>/dev/null || echo "0")
[ "$enable_value" -ne "0" ] && procd_set_param command /usr/share/wechatpush/wechatpush && echo "wechatpush is starting now ..."
procd_close_instance
}
reload_service() {
stop
sleep 1
start
}
stop_service() {
[ -f ${dir}child_pid ] && parent_pid=$(cat ${dir}child_pid)
[ -n "$parent_pid" ] && {
child_pids=$(pgrep -P $parent_pid)
echo "Terminating child processes of wechatpush..."
for child_pid in $child_pids; do
kill $child_pid
done
}
local pids=$(ps | grep "wechatpush" | grep -v grep | grep -v $$ | awk '{print $1}')
[ -n "$pids" ] && echo "$pids" | xargs kill 2>/dev/null
echo "Terminating wechatpush process..."
}
service_triggers() {
procd_add_reload_trigger wechatpush
}
@@ -1,15 +0,0 @@
#!/bin/sh
function get_config_wechatpush(){
while [[ "$*" != "" ]]; do
eval ${1}='`uci get wechatpush.config.$1`' 2>/dev/null
uci del wechatpush.config.$1 2>/dev/null
uci commit wechatpush
shift
done
}
get_config_wechatpush "chat_id"
[ -n "$chat_id" ] && uci set wechatpush.config.tg_chat_id="$chat_id"
rm -rf /tmp/luci-* >/dev/null 2>&1
@@ -1,96 +0,0 @@
#!/bin/sh
logfile="/tmp/wechatpush/wechatpush.log"
dir="/tmp/wechatpush" && mkdir -p "${dir}"
oui_base="${dir}/oui_base.txt"
oui_data=$(uci get wechatpush.config.oui_data 2>/dev/null)
oui_url="https://standards-oui.ieee.org/oui/oui.txt"
lang=$(uci get luci.main.lang 2>/dev/null)
if [ -z "$lang" ] || [[ "$lang" == "auto" ]]; then
lang=$(echo "${LANG:-${LANGUAGE:-${LC_ALL:-${LC_MESSAGES:-zh_cn}}}}" | awk -F'[ .@]' '{print tolower($1)}' | sed 's/-/_/' 2>/dev/null)
fi
translate() {
# 处理特殊字符
local lua_script=$(cat <<LUA
require "luci.i18n".setlanguage("$lang")
print(require "luci.i18n".translate([==[$1]==]))
LUA
)
lua -e "$lua_script"
}
# 获取文件最后修改时间和行数
file_date() {
local file_dir="$1"
local filerow=$(grep -c "" "${file_dir}" 2>/dev/null || echo "0")
local datetime=$(date -r "${file_dir}" +%s 2>/dev/null || echo "0")
expr $(date +%s) - ${datetime}
}
# 下载
download_file() {
url=$1
output_file=$2
retries=2
retval=1
# 尝试使用 curl 下载
i=0
while [ $i -lt $retries ]; do
curl -fsSL --connect-timeout 15 --max-time 30 -o "${output_file}" "${url}" >/dev/null 2>&1
retval=$?
if [ $retval -eq 0 ]; then
return 0
fi
i=$((i + 1))
sleep 1 # 等待1秒后重试
done
# 如果 curl 失败,尝试使用 wget 下载
i=0
while [ $i -lt $retries ]; do
wget --no-check-certificate -T 15 -O "${output_file}" "${url}" >/dev/null 2>&1
retval=$?
if [ $retval -eq 0 ]; then
return 0
fi
i=$((i + 1))
sleep 1 # 等待1秒后重试
done
return $retval
}
# 更新 MAC 信息文件
down_oui() {
if [ -n "${oui_data}" ] && [ "${oui_data}" -ne "3" ]; then
echo "$(date "+%Y-%m-%d %H:%M:%S") [INFO] $(translate "Starting background update of MAC information file")" >>"${logfile}"
download_file "${oui_url}" "${dir}/oui.txt"
local RETVAL=$?
[ ${RETVAL} -ne 0 ] && echo "$(date "+%Y-%m-%d %H:%M:%S") [ERROR] $(translate "Failed to download MAC information file, return code: ${RETVAL}")" >>"${logfile}" && return 1
echo "$(date "+%Y-%m-%d %H:%M:%S") [INFO] $(translate "MAC information file downloaded successfully, processing...")" >>"${logfile}"
if [ "${oui_data}" -eq "1" ]; then
grep -i -E ".*(base 16).*(apple|aruba|asus|autelan|belkin|bhu|buffalo|cctf|cisco|comba|datang|dell|dlink|dowell|ericsson|fast|feixun|fiberhome|fujitsu|grentech|h3c|hisense|hiwifi|honghai|honghao|hp|htc|huawei|intel|jinli|jse|lenovo|lg|liteon|malata|meizu|mercury|meru|moto|netcore|netgear|nokia|omron|oneplus|oppo|philips|router_unkown|samsung|shanzhai|sony|start_net|sunyuanda|tcl|tenda|texas|tianyu|tp-link|ubq|undefine|VMware|utstarcom|volans|xerox|xiaomi|zdc|zhongxing|smartisan).*" "${dir}/oui.txt" | sed -E 's/( Electronic| Technology| Intelligence| TECHNOLOGIES| Device| Systems| TELECOMMUNICATIONS| Instruments| Electronics| Corporation| Telecommunication| Communications| Electrical| Technology| Corporate| Intelligent| Interactive| MOBILE| Solutions| Mobility| Meraki| ELECTRO| VISUAL| Limited| International| Information| LLC|Co$|Co\.|Ltd\.$|Inc\.|B\.V\.$|AB$|,).*$/ /I; s/[[:space:]]*$//; s/ +$//' >"${oui_base}"
elif [ "${oui_data}" -eq "2" ]; then
grep -i "(base 16)" "${dir}/oui.txt" | sed -E 's/( Electronic| Technology| Intelligence| TECHNOLOGIES| Device| Systems| TELECOMMUNICATIONS| Instruments| Electronics| Corporation| Telecommunication| Communications| Electrical| Technology| Corporate| Intelligent| Interactive| MOBILE| Solutions| Mobility| Meraki| ELECTRO| VISUAL| Limited| International| Information| LLC|Co$|Co\.|Ltd\.$|Inc\.|B\.V\.$|AB$|,).*$/ /I; s/[[:space:]]*$//; s/ +$//' >"${oui_base}"
fi
rm -f "${dir}/oui.txt" >/dev/null 2>&1
echo "$(date "+%Y-%m-%d %H:%M:%S") [INFO] $(translate "MAC information file processing completed")" >>"${logfile}"
fi
}
if [ "$1" == "clear_log" ]; then
# 清空日志
>"${logfile}"
elif [ "$1" == "down_oui" ]; then
# 更新 MAC 信息列表
[ $(file_date "$oui_base") -lt 604800 ] && echo "$(date "+%Y-%m-%d %H:%M:%S") [INFO] $(translate "MAC information file does not need updating")" >>"${logfile}" && exit 2
down_oui >/dev/null 2>&1 &
elif [ "$1" == "child" ]; then
shift
command_name=$1
shift
"$command_name" "$@"
fi
@@ -1,50 +0,0 @@
{
"admin/services/wechatpush": {
"title": "WeChat push",
"order": 30,
"action": {
"type": "view",
"path": "wechatpush/status"
},
"depends": {
"acl": [ "luci-app-wechatpush" ],
"uci": { "wechatpush": true }
}
},
"admin/services/wechatpush/config": {
"title": "Basic Settings",
"order": 10,
"action": {
"type": "view",
"path": "wechatpush/config"
}
},
"admin/services/wechatpush/advanced": {
"title": "Advance Setting",
"order": 20,
"action": {
"type": "view",
"path": "wechatpush/advanced"
}
},
"admin/services/wechatpush/client": {
"title": "Online Devices",
"order": 30,
"action": {
"type": "view",
"path": "wechatpush/client"
}
},
"admin/services/wechatpush/log": {
"title": "Log",
"order": 40,
"action": {
"type": "view",
"path": "wechatpush/log"
}
}
}
@@ -1,26 +0,0 @@
{
"luci-app-wechatpush": {
"description": "Grant UCI access for luci-app-wechatpush",
"read": {
"file": {
"/etc/init.d/wechatpush": [ "exec" ],
"/usr/share/wechatpush/wechatpush": [ "exec" ],
"/tmp/wechatpush/*": [ "read" ],
"/usr/share/wechatpush/api/*": [ "read" ],
"/usr/libexec/wechatpush-call": [ "exec" ]
},
"ubus": {
"luci-rpc": [ "getHostHints" ],
"service": [ "list" ]
},
"uci": [ "wechatpush" ]
},
"write": {
"file": {
"/usr/share/wechatpush/api/*": [ "write" ],
"/tmp/wechatpush/*": [ "write" ]
},
"uci": [ "wechatpush" ]
}
}
}
@@ -1,3 +0,0 @@
# Examples :
#XX:XX:XX:XX:XX:XX My Phone
#192.168.1.2 My PC
@@ -1,42 +0,0 @@
{
"_//": "-------------------------------------------------------------------------------",
"_readme": "这是 自定义 api 文件,这里以 telegram 为例",
"_readme": "特殊符号请使用斜杠转义,变量使用 ${var} 表示",
"_//": "-------------------------------------------------------------------------------",
"_api": "【DIY 推送】",
"_url": "api 地址",
"_data": "生成的 json 文件路径,一般不需要改,如 api 不支持 json,请参考 serverchan 推送接口",
"_content_type": "post 内容类型,这里为 json",
"_//": "-------------------------------------------------------------------------------",
"_str_title_start": "标题粗体字开始符号",
"_str_title_end": "标题粗体字结束符号",
"_str_linefeed": "换行符号",
"_str_splitline": "换行+分隔符",
"_str_space": "空格",
"_str_tab": "TAB(用在行首,生成文字区块)",
"_//": "-------------------------------------------------------------------------------",
"_type":
{
"_readme": "下文中,text 为 telegram 推送需要的键值名称,参见 telegram 官方文档,后面的内容为生成标题所需要的字符串和变量,${1} 为标题内容变量,${nowtime} 为推送时间,${2} 为推送内容变量",
"_readme": "下文中,chat_id 为 telegram 推送需要的键值名称,参见 telegram 官方文档,${tg_chat_id} 为从脚本配置中读取名为 tg_chat_id 的变量,其实就是你填写的机器人的 chat_id ",
"_readme": "type 对象因为需要转义变量,前后必须使用 斜杠+双引号 转义",
"_readme": "参照上文说明,填写下文相关参数"
},
"_//": "-------------------------------------------------------------------------------",
"url": "${tg_api_server}/bot${tg_token}/sendMessage",
"data": "@${tempjsonpath}",
"content_type": "Content-Type: application/json",
"str_title_start": "<b>",
"str_title_end": "</b>",
"str_linefeed": "\\n",
"str_splitline": "\\n----\\n",
"str_space": " ",
"str_tab": " ",
"type":
{
"text":"\"${str_title_start}${1}${str_title_end}${str_splitline}${nowtime}${2}\"",
"chat_id":"\"${tg_chat_id}\"",
"parse_mode":"\"HTML\""
}
}
@@ -1,3 +0,0 @@
https://ip.rss.ink/v1/qqwry?ip=${ip} | jq -r '.data.area'
ip.plus/${ip} | sed -n 's/.*来自: //p'
http://ip-api.com/json/${ip}?lang=zh-CN | jq -r '"\(.country) \(.regionName) \(.city)"'
Whitespace-only changes.
@@ -1,8 +0,0 @@
ddns.oray.com/checkip
www.net.cn/static/customercare/yourip.asp
ip.3322.net
ip.threep.top
ip.atomo.cn
ip.ddnspod.com
4.ipw.cn
ipv4.ip.mir6.com
@@ -1,6 +0,0 @@
speed.neu6.edu.cn/getIP.php
6.ipw.cn
ip.atomo.cn
ip.ddnspod.com
v6.ip.zxinc.org/getip
ipv6.ip.mir6.com
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

@@ -1,21 +0,0 @@
{
"_api": "这是 msmtp api 文件",
"_api": "【msmtp】",
"url": "msmtp --host=${smtp_host} --port=${smtp_port} ${smtp_tls:+--tls=$smtp_tls} ${smtp_starttls:+--tls-starttls=$smtp_starttls} ${smtp_user:+--auth=on --user=$smtp_user} ${smtp_passwordeval:+--passwordeval=\\\"$smtp_passwordeval\\\"} ${smtp_from:+--from=$smtp_from} ${smtp_from_full_name:+-F\\\"$smtp_from_full_name\\\"} ${recipient_email}",
"data": "${tempjsonpath}",
"content_type": "",
"command": "'\"To: \\(.to)\\nSubject: \\(.subject)\\nContent-Type: text/html\\n\\n\\(.body)\"'",
"str_title_start": "<br><h3 style=\\\"margin: 0em 0em;\\\">",
"str_title_end": "</h3>",
"str_linefeed": "<br>",
"str_splitline": "<br><br><hr>",
"str_space": " ",
"str_tab": " ",
"type":
{
"to":"\"${recipient_email}\"",
"subject":"\"${1}\"",
"body":"\"${nowtime}${2}\""
}
}
@@ -1,21 +0,0 @@
{
"_api": "这是 pushplus api 文件",
"_api": "【pushplus】",
"url": "http://www.pushplus.plus/send",
"data": "@${tempjsonpath}",
"content_type": "Content-Type: application/json",
"str_title_start": "##### ",
"str_title_end": "",
"str_linefeed": "\\n",
"str_splitline": "\\n----\\n",
"str_space": " ",
"str_tab": " ",
"type":
{
"title": "\"${1}\"",
"content": "\"${2}\"",
"token": "\"${pushplus_token}\"",
"template":"\"markdown\""
}
}
@@ -1,23 +0,0 @@
{
"_api": "这是企业微信 markdown 模板信息 api 文件",
"_api": "【企业微信】",
"url": "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=$(curl -s \"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpid}&corpsecret=${corpsecret}\"|jq '.access_token' | sed 's/\"//g')",
"data": "@${tempjsonpath}",
"content_type": "Content-Type: application/json",
"str_title_start": "#### ",
"str_title_end": "",
"str_linefeed": "\\n",
"str_splitline": "\\n\\n",
"str_space": " ",
"str_tab": "> ",
"type":
{
"touser": "\"${userid}\"",
"msgtype": "\"markdown\"",
"agentid": "\"${agentid}\"",
"markdown": {
"content": "\"${1}${str_linefeed}${nowtime}${2}\""
}
}
}
@@ -1,33 +0,0 @@
{
"_api": "这是企业微信图文信息 api 文件",
"_api": "【企业微信】",
"url": "\"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=$(curl -s \"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpid}&corpsecret=${corpsecret}\"|jq '.access_token'|sed 's/\"//g')\"",
"data": "@${tempjsonpath}",
"content_type": "Content-Type: application/json",
"str_title_start": "<b>",
"str_title_end": "</b>",
"str_linefeed": "<br>",
"str_splitline": "<hr>",
"str_space": " ",
"str_tab": "",
"type":
{
"touser": "\"${userid}\"",
"msgtype": "\"mpnews\"",
"agentid": "\"${agentid}\"",
"mpnews":{
"articles":[
{
"title": "\"${1} ${nowtime}\"",
"thumb_media_id": "\"`curl \"https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=$(curl -s \"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpid}&corpsecret=${corpsecret}\"|jq '.access_token'|sed 's/\"//g')&type=image\" -F \"file=@${mediapath}\"|jq '.media_id'|sed 's/\"//g'`\"",
"author": "\"\"",
"content_source_url": "\"\"",
"content": "\"${2}\"",
"digest": "\"\""
}
]
},
"safe":0
}
}
@@ -1,17 +0,0 @@
{
"_api": "这是 serverchan api 文件",
"_api": "【serverchan】",
"url": "\"https://sctapi.ftqq.com/${sckey}.send\"",
"data": "\"text=${1}&desp=${nowtime}${str_linefeed}${2}\"",
"content_type": "Content-Type:application/x-www-form-urlencoded",
"str_title_start": "#### ",
"str_title_end": "",
"str_linefeed": "%0D%0A%0D%0A",
"str_splitline": "%0D%0A%0D%0A----%0D%0A%0D%0A",
"str_space": " ",
"str_tab": " ",
"type":
{
}
}
@@ -1,20 +0,0 @@
{
"_api": "这是 serverchan3 api 文件",
"_api": "【serverchan3】",
"url": "\"https://${sc3uid}.push.ft07.com/send/${sc3key}.send\"",
"data": "@${tempjsonpath}",
"content_type": "Content-Type: application/json",
"str_title_start": "##### ",
"str_title_end": "",
"str_linefeed": " \\n",
"str_splitline": "\\n----\\n",
"str_space": " ",
"str_tab": " ",
"type":
{
"title": "\"${1}\"",
"desp": "\"${nowtime}${str_linefeed}${2}\"",
"tags": "\"${sc3tags}\""
}
}
@@ -1,21 +0,0 @@
{
"_api": "这是 telegram api 文件",
"_api": "【telegram】",
"url": "${tg_api_server}/bot${tg_token}/sendMessage",
"data": "@${tempjsonpath}",
"content_type": "Content-Type: application/json",
"str_title_start": "<b>",
"str_title_end": "</b>",
"str_linefeed": "\\n",
"str_splitline": "\\n----\\n",
"str_space": " ",
"str_tab": "\\t",
"type":
{
"text":"\"${str_title_start}${1}${str_title_end}${str_splitline}${nowtime}${2}\"",
"chat_id":"\"${tg_chat_id}\"",
"message_thread_id":"\"${tg_thread_id}\"",
"parse_mode":"\"HTML\""
}
}
@@ -1,21 +0,0 @@
{
"_api": "这是 wpush api 文件",
"_api": "【wpush】",
"_readme": "成功判定:响应 JSON 的 application code===0(pushplus 为 code===200;本插件与 pushplus 相同,diy_send 仅检查 curl 退出码)",
"url": "https://api.wpush.cn/api/v1/send",
"data": "@${tempjsonpath}",
"content_type": "Content-Type: application/json",
"str_title_start": "##### ",
"str_title_end": "",
"str_linefeed": "\\n",
"str_splitline": "\\n----\\n",
"str_space": " ",
"str_tab": " ",
"type":
{
"title": "\"${1}\"",
"content": "\"${2}\"",
"apikey": "\"${wpush_apikey}\""
}
}
@@ -1,23 +0,0 @@
{
"_api": "这是 wxpusher api 文件",
"_api": "【wxpusher】",
"url": "http://wxpusher.zjiecode.com/api/send/message",
"data": "@${tempjsonpath}",
"content_type": "Content-Type: application/json",
"str_title_start": "#### ",
"str_title_end": "",
"str_linefeed": "\\n",
"str_splitline": "\\n----\\n",
"str_space": " ",
"str_tab": " ",
"type":
{
"summary":"\"${1}\"",
"content":"\"${2}\"",
"appToken":"\"${wxpusher_apptoken}\"",
"topicIds":"[\"${wxpusher_topicIds}\"]",
"uids":"[\"${wxpusher_uids}\"]",
"contentType":3
}
}
File diff suppressed because it is too large. Load diff