初步优化通知

This commit is contained in:
wushuo
2025-06-18 16:32:54 +08:00
parent c7a5663341
commit fd31a147e7
48 changed files with 1146 additions and 1013 deletions

View File

@@ -19,7 +19,7 @@
![GitHub License](https://img.shields.io/github/license/wushuo894/ani-rss)
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/wushuo894/ani-rss?color=blue&label=download&sort=semver)](https://github.com/wushuo894/ani-rss/releases/latest)
[![GitHub all releases](https://img.shields.io/github/downloads/wushuo894/ani-rss/total?color=blue&label=github%20downloads)](https://docs.wushuo.top/history)
[![telegram](https://img.shields.io/static/v1?label=telegram&message=ani-rss&color=blue)](https://t.me/ani_rss)
[![telegram](https://img.shields.io/static/v1?label=telegram&baseNotification=ani-rss&color=blue)](https://t.me/ani_rss)
</div>

View File

@@ -131,12 +131,6 @@ verify_install() {
exit 1
fi
# sleep 10
# if ! ss -tulnp | grep -q ":$PORT"; then
# echo -e "${RED}端口 $PORT 未监听${NC}"
# exit 1
# fi
echo -e "${GREEN}验证通过,服务运行正常${NC}"
}

View File

@@ -32,7 +32,7 @@ public class BgmAction implements BaseAction {
case "getAniBySubjectId" -> {
String id = request.getParam("id");
BgmInfo bgmInfo = BgmUtil.getBgmInfo(id, true);
Ani ani = BgmUtil.toAni(bgmInfo, Ani.bulidAni());
Ani ani = BgmUtil.toAni(bgmInfo, Ani.createAni());
ani
.setCustomDownloadPath(true);
resultSuccess(ani);

View File

@@ -7,7 +7,6 @@ import ani.rss.util.AniUtil;
import ani.rss.util.ConfigUtil;
import ani.rss.util.FilePathUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;

View File

@@ -12,7 +12,6 @@ import cn.hutool.http.server.HttpServerResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

View File

@@ -2,9 +2,8 @@ package ani.rss.action;
import ani.rss.annotation.Auth;
import ani.rss.annotation.Path;
import ani.rss.entity.Config;
import ani.rss.entity.EmbyViews;
import ani.rss.util.ConfigUtil;
import ani.rss.entity.NotificationConfig;
import ani.rss.util.EmbyUtil;
import cn.hutool.http.server.HttpServerRequest;
import cn.hutool.http.server.HttpServerResponse;
@@ -21,18 +20,17 @@ public class EmbyAction implements BaseAction {
@Override
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
Config config = getBody(Config.class);
ConfigUtil.format(config);
NotificationConfig notificationConfig = getBody(NotificationConfig.class);
String type = request.getParam("type");
if (type.equals("getViews")) {
List<EmbyViews> views = EmbyUtil.getViews(config);
List<EmbyViews> views = EmbyUtil.getViews(notificationConfig);
resultSuccess(views);
return;
}
if (type.equals("refresh")) {
EmbyUtil.refresh(config);
EmbyUtil.refresh(notificationConfig);
resultSuccess();
}

View File

@@ -4,16 +4,14 @@ import ani.rss.annotation.Auth;
import ani.rss.annotation.Path;
import ani.rss.entity.Ani;
import ani.rss.entity.BgmInfo;
import ani.rss.entity.Config;
import ani.rss.entity.NotificationConfig;
import ani.rss.entity.Tmdb;
import ani.rss.enums.MessageEnum;
import ani.rss.msg.Message;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.enums.NotificationTypeEnum;
import ani.rss.notification.BaseNotification;
import ani.rss.util.AniUtil;
import ani.rss.util.BgmUtil;
import ani.rss.util.ConfigUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.server.HttpServerRequest;
import cn.hutool.http.server.HttpServerResponse;
@@ -24,20 +22,32 @@ import java.util.Date;
* 通知
*/
@Auth
@Path("/message")
public class MessageAction implements BaseAction {
@Path("/notification")
public class NotificationAction implements BaseAction {
@Override
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
String s = request.getParam("type");
if (StrUtil.isBlank(s)) {
resultError();
String type = request.getParam("type");
if ("test".equals(type)) {
test();
return;
}
Config config = getBody(Config.class);
ConfigUtil.format(config);
Class<Object> loadClass = ClassUtil.loadClass("ani.rss.msg." + s);
Message message = (Message) ReflectUtil.newInstance(loadClass);
Ani ani = Ani.bulidAni();
if ("add".equals(type)) {
add();
}
}
private void add() {
NotificationConfig notificationConfig = NotificationConfig.createNotificationConfig();
resultSuccess(notificationConfig);
}
private void test() {
NotificationConfig notificationConfig = getBody(NotificationConfig.class);
NotificationTypeEnum notificationType = notificationConfig.getNotificationType();
BaseNotification baseNotification = ReflectUtil.newInstance(notificationType.getAClass());
Ani ani = Ani.createAni();
ani.setBgmUrl("https://bgm.tv/subject/424883");
BgmInfo bgmInfo = BgmUtil.getBgmInfo(ani);
String image = bgmInfo.getImage();
@@ -57,11 +67,12 @@ public class MessageAction implements BaseAction {
.setName("不时用俄语小声说真心话的邻桌艾莉同学")
.setDate(new Date())
);
Boolean test = message.send(config, ani, "test", MessageEnum.DOWNLOAD_START);
Boolean test = baseNotification.send(notificationConfig, ani, "test", NotificationStatusEnum.DOWNLOAD_START);
if (test) {
resultSuccess();
return;
}
resultError();
}
}

View File

@@ -2,9 +2,8 @@ package ani.rss.action;
import ani.rss.annotation.Auth;
import ani.rss.annotation.Path;
import ani.rss.entity.Config;
import ani.rss.msg.Telegram;
import ani.rss.util.ConfigUtil;
import ani.rss.entity.NotificationConfig;
import ani.rss.notification.TelegramNotification;
import cn.hutool.http.server.HttpServerRequest;
import cn.hutool.http.server.HttpServerResponse;
@@ -20,14 +19,12 @@ public class TelegramAction implements BaseAction {
@Override
public void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException {
Config config = getBody(Config.class);
ConfigUtil.format(config);
NotificationConfig notificationConfig = getBody(NotificationConfig.class);
String method = request.getParam("method");
if ("getUpdates".equals(method)) {
Map<String, String> map = Telegram.getUpdates(config);
Map<String, String> map = TelegramNotification.getUpdates(notificationConfig);
resultSuccess(map);
}
}
}

View File

@@ -4,7 +4,7 @@ import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.entity.Item;
import ani.rss.entity.TorrentsInfo;
import ani.rss.enums.MessageEnum;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.enums.StringEnum;
import ani.rss.util.*;
import cn.hutool.core.collection.ListUtil;
@@ -252,9 +252,9 @@ public class Alist implements BaseDownload {
"names", List.of(reName)
))).then(HttpResponse::isOk);
MessageUtil.send(config, ani,
NotificationUtil.send(config, ani,
StrFormatter.format("{} 下载完成", item.getReName()),
MessageEnum.DOWNLOAD_END
NotificationStatusEnum.DOWNLOAD_END
);
return true;
} catch (Exception e) {

View File

@@ -216,7 +216,7 @@ public class Ani implements Serializable {
*/
private Boolean message;
public static Ani bulidAni() {
public static Ani createAni() {
Ani newAni = new Ani();
Config config = ConfigUtil.CONFIG;
return newAni

View File

@@ -1,6 +1,5 @@
package ani.rss.entity;
import ani.rss.enums.MessageEnum;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -228,26 +227,6 @@ public class Config implements Serializable {
*/
private Integer downloadCount;
/**
* 邮箱是否开启
*/
private Boolean mail;
/**
* 发件人
*/
private MyMailAccount mailAccount;
/**
* 收件人
*/
private String mailAddressee;
/**
* mail 发送图片
*/
private Boolean mailImage;
/**
* 登录信息
*/
@@ -278,61 +257,6 @@ public class Config implements Serializable {
*/
private Boolean enabledExclude;
/**
* telegram
*/
private Boolean telegram;
/**
* telegram bot token
*/
private String telegramBotToken;
/**
* telegram chat_id
*/
private String telegramChatId;
/**
* telegram topic id
*/
private Integer telegramTopicId;
/**
* telegram Api Host
*/
private String telegramApiHost;
/**
* telegram 发送图片
*/
private Boolean telegramImage;
/**
* telegram 格式
*/
private String telegramFormat;
/**
* webHookMethod
*/
private String webHookMethod;
/**
* webHookUrl
*/
private String webHookUrl;
/**
* webHookBody
*/
private String webHookBody;
/**
* webHook
*/
private Boolean webHook;
/**
* BGM日文标题
*/
@@ -418,46 +342,11 @@ public class Config implements Serializable {
*/
private Boolean renameDelTmdbId;
/**
* 通知类型
*/
private List<MessageEnum> messageList;
/**
* 校验登录IP
*/
private Boolean verifyLoginIp;
/**
* server酱类型server酱和server酱3
*/
private String serverChanType;
/**
* server酱 sendKey
*/
private String serverChanSendKey;
/**
* server酱3 apiUrl
*/
private String serverChan3ApiUrl;
/**
* server酱 标题事件
*/
private Boolean serverChanTitleAction;
/**
* server酱 开关
*/
private Boolean serverChan;
/**
* 系统通知
*/
private Boolean systemMsg;
/**
* 自动更新 trackers
*/
@@ -471,7 +360,7 @@ public class Config implements Serializable {
/**
* 消息模版
*/
private String messageTemplate;
private String notificationTemplate;
/**
* 自动更新
@@ -588,31 +477,6 @@ public class Config implements Serializable {
*/
private Boolean tryOut;
/**
* emby扫描媒体库
*/
private Boolean embyRefresh;
/**
* emby地址
*/
private String embyHost;
/**
* emby api密钥
*/
private String embyApiKey;
/**
* emby扫描媒体库
*/
private List<String> embyRefreshViewIds;
/**
* emby延迟扫描
*/
private Long embyDelayed;
/**
* 摸鱼
*/
@@ -697,4 +561,9 @@ public class Config implements Serializable {
* 番剧完结迁移位置
*/
private String completedPathTemplate;
/**
* 通知
*/
private List<NotificationConfig> notificationConfigList;
}

View File

@@ -1,24 +0,0 @@
package ani.rss.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 邮箱通知的配置
* <p>
* 具体注释见
*
* @see cn.hutool.extra.mail.MailAccount
*/
@Data
@Accessors(chain = true)
public class MyMailAccount implements Serializable {
private String host;
private Integer port;
private String from;
private String pass;
private Boolean sslEnable;
private Boolean starttlsEnable;
}

View File

@@ -0,0 +1,219 @@
package ani.rss.entity;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.enums.NotificationTypeEnum;
import ani.rss.enums.ServerChanTypeEnum;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
@Accessors(chain = true)
public class NotificationConfig implements Serializable {
/**
* 启用
*/
private Boolean enable;
/**
* 备注
*/
private String comment;
/**
* 通知模版
*/
private String notificationTemplate;
/**
* 通知类型
*/
private NotificationTypeEnum notificationType;
/**
* 邮箱 smtp
*/
private String mailSMTPHost;
/**
* 邮箱 端口
*/
private Integer mailSMTPPort;
/**
* 邮箱 发件人
*/
private String mailFrom;
/**
* 邮箱 密码
*/
private String mailPassword;
/**
* 邮箱 SSL
*/
private Boolean mailSSLEnable;
/**
* 邮箱 TLS
*/
private Boolean mailTLSEnable;
/**
* 邮箱 收件人
*/
private String mailAddressee;
/**
* 邮箱 发送图片
*/
private Boolean mailImage;
/**
* server酱类型server酱和server酱3
*/
private ServerChanTypeEnum serverChanType;
/**
* server酱 sendKey
*/
private String serverChanSendKey;
/**
* server酱3 apiUrl
*/
private String serverChan3ApiUrl;
/**
* server酱 标题事件
*/
private Boolean serverChanTitleAction;
/**
* 系统通知
*/
private Boolean systemMsg;
/**
* telegram bot token
*/
private String telegramBotToken;
/**
* telegram chat_id
*/
private String telegramChatId;
/**
* telegram topic id
*/
private Integer telegramTopicId;
/**
* telegram Api Host
*/
private String telegramApiHost;
/**
* telegram 发送图片
*/
private Boolean telegramImage;
/**
* telegram 格式
*/
private String telegramFormat;
/**
* webHookMethod
*/
private String webHookMethod;
/**
* webHookUrl
*/
private String webHookUrl;
/**
* webHookBody
*/
private String webHookBody;
/**
* emby扫描媒体库
*/
private Boolean embyRefresh;
/**
* emby地址
*/
private String embyHost;
/**
* emby api密钥
*/
private String embyApiKey;
/**
* emby扫描媒体库
*/
private List<String> embyRefreshViewIds;
/**
* emby延迟扫描
*/
private Long embyDelayed;
/**
* 通知 状态
*/
private List<NotificationStatusEnum> statusList;
public static NotificationConfig createNotificationConfig() {
NotificationConfig notificationConfig = new NotificationConfig();
notificationConfig
.setNotificationType(NotificationTypeEnum.TELEGRAM)
.setNotificationTemplate("${notification}")
.setComment("")
.setStatusList(List.of(
NotificationStatusEnum.DOWNLOAD_START,
NotificationStatusEnum.OMIT,
NotificationStatusEnum.ERROR
));
// 邮箱
notificationConfig
.setMailSMTPHost("smtp.qq.com")
.setMailSMTPPort(465)
.setMailFrom("")
.setMailPassword("")
.setMailAddressee("")
.setMailImage(true)
.setMailSSLEnable(true)
.setMailTLSEnable(false);
// telegram
notificationConfig
.setTelegramChatId("")
.setTelegramBotToken("")
.setTelegramApiHost("https://api.telegram.org")
.setTelegramImage(true)
.setTelegramTopicId(-1)
.setTelegramFormat("");
// webhook
notificationConfig
.setWebHookBody("")
.setWebHookUrl("")
.setWebHookMethod("POST");
// server-chan
notificationConfig
.setServerChanType(ServerChanTypeEnum.SERVER_CHAN)
.setServerChanSendKey("")
.setServerChan3ApiUrl("")
.setServerChanTitleAction(true);
// emby
notificationConfig
.setEmbyRefresh(false)
.setEmbyApiKey("")
.setEmbyRefreshViewIds(new ArrayList<>())
.setEmbyDelayed(0L);
return notificationConfig;
}
}

View File

@@ -3,7 +3,7 @@ package ani.rss.enums;
import lombok.Getter;
@Getter
public enum MessageEnum {
public enum NotificationStatusEnum {
/**
* 开始下载
*/
@@ -36,7 +36,7 @@ public enum MessageEnum {
private final String emoji;
private final String action;
MessageEnum(String emoji, String action) {
NotificationStatusEnum(String emoji, String action) {
this.emoji = emoji;
this.action = action;
}

View File

@@ -0,0 +1,18 @@
package ani.rss.enums;
import ani.rss.notification.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
public enum NotificationTypeEnum {
EMBY_REFRESH(EmbyRefreshNotification.class),
MAIL(MailNotification.class),
SERVER_CHAN(ServerChanNotification.class),
SYSTEM(SystemNotification.class),
TELEGRAM(TelegramNotification.class),
WEB_HOOK(WebHookNotification.class);
@Getter
private final Class<? extends BaseNotification> aClass;
}

View File

@@ -1,72 +0,0 @@
package ani.rss.msg;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.entity.MyMailAccount;
import ani.rss.enums.MessageEnum;
import ani.rss.util.ExceptionUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Objects;
/**
* 邮箱
*/
@Slf4j
public class Mail implements Message {
@Override
public Boolean send(Config config, Ani ani, String text, MessageEnum messageEnum) {
String messageBody = replaceMessageTemplate(ani, config.getMessageTemplate(), text, messageEnum);
Boolean mail = config.getMail();
MyMailAccount myMailAccount = config.getMailAccount();
String mailAddressee = config.getMailAddressee();
if (!mail) {
return false;
}
String from = myMailAccount.getFrom();
String host = myMailAccount.getHost();
String pass = myMailAccount.getPass();
Assert.notBlank(from, "发件人邮箱 为空");
Assert.notBlank(host, "SMTP地址 为空");
Assert.notBlank(pass, "密码 为空");
Assert.notBlank(mailAddressee, "收件人 为空");
MailAccount mailAccount = new MailAccount();
BeanUtil.copyProperties(myMailAccount, mailAccount, CopyOptions
.create()
.setIgnoreNullValue(true));
mailAccount.setUser(from)
.setFrom(StrFormatter.format("ani-rss <{}>", from))
.setAuth(true);
messageBody = messageBody.replace("\n", "<br/>");
Boolean mailImage = config.getMailImage();
if (mailImage) {
String image = "https://docs.wushuo.top/null.png";
if (Objects.nonNull(ani)) {
image = ani.getImage();
}
messageBody += StrFormatter.format("<br/><img src=\"{}\"/>", image);
}
try {
MailUtil.send(mailAccount, List.of(mailAddressee), text.length() > 200 ? ani.getTitle() : text, messageBody, true);
return true;
} catch (Exception e) {
String message = ExceptionUtil.getMessage(e);
log.error(message, e);
return false;
}
}
}

View File

@@ -1,38 +0,0 @@
package ani.rss.msg;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.enums.MessageEnum;
import ani.rss.util.MenuUtil;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import java.util.Objects;
/**
* SystemMsg
*/
@Slf4j
public class SystemMsg implements Message {
@Override
public Boolean send(Config config, Ani ani, String text, MessageEnum messageEnum) {
text = replaceMessageTemplate(ani, config.getMessageTemplate(), text, messageEnum);
if (!SystemTray.isSupported()) {
log.error("SystemTray is not supported");
return false;
}
TrayIcon trayIcon = MenuUtil.trayIcon;
if (Objects.isNull(trayIcon)) {
log.error("未开启系统托盘 添加--gui参数启动");
return false;
}
TrayIcon.MessageType type = TrayIcon.MessageType.INFO;
if (Objects.nonNull(messageEnum)) {
if (messageEnum.name().equals(MessageEnum.ERROR.name())) {
type = TrayIcon.MessageType.ERROR;
}
}
trayIcon.displayMessage("ani-rss", text, type);
return true;
}
}

View File

@@ -1,58 +0,0 @@
package ani.rss.msg;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.enums.MessageEnum;
import ani.rss.util.HttpReq;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
/**
* WebHook
*/
@Slf4j
public class WebHook implements Message {
@Override
public Boolean send(Config config, Ani ani, String text, MessageEnum messageEnum) {
String messageTemplate = config.getMessageTemplate();
messageTemplate = messageTemplate.replace("\n", "\\n");
String message = replaceMessageTemplate(ani, messageTemplate, text, messageEnum);
String webHookMethod = config.getWebHookMethod();
String webHookUrl = config.getWebHookUrl();
String webHookBody = config.getWebHookBody();
webHookUrl = replaceMessageTemplate(ani, webHookUrl, text, messageEnum);
webHookBody = replaceMessageTemplate(ani, webHookBody, text, messageEnum);
if (StrUtil.isBlank(webHookUrl)) {
log.warn("webhook url is blank");
return false;
}
webHookUrl = webHookUrl.replace("${message}", message);
webHookBody = webHookBody.replace("${message}", message);
String image = "https://docs.wushuo.top/null.png";
if (Objects.nonNull(ani) && StrUtil.isNotBlank(ani.getImage())) {
image = ani.getImage();
}
webHookUrl = webHookUrl.replace("${image}", image);
webHookBody = webHookBody.replace("${image}", image);
HttpRequest httpRequest = HttpReq.get(webHookUrl, true)
.method(Method.valueOf(webHookMethod));
if (StrUtil.isNotBlank(webHookBody)) {
httpRequest.body(webHookBody);
}
return httpRequest.thenFunction(HttpResponse::isOk);
}
}

View File

@@ -1,9 +1,9 @@
package ani.rss.msg;
package ani.rss.notification;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.entity.NotificationConfig;
import ani.rss.entity.Tmdb;
import ani.rss.enums.MessageEnum;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.enums.StringEnum;
import ani.rss.util.FilePathUtil;
import ani.rss.util.NumberFormatUtil;
@@ -18,12 +18,12 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
public interface Message {
public interface BaseNotification {
Boolean send(Config config, Ani ani, String text, MessageEnum messageEnum);
Boolean send(NotificationConfig notificationConfig, Ani ani, String text, NotificationStatusEnum notificationStatusEnum);
default String replaceMessageTemplate(Ani ani, String messageTemplate, String text, MessageEnum messageEnum) {
messageTemplate = messageTemplate.replace("${text}", text);
default String replaceNotificationTemplate(Ani ani, String notificationTemplate, String text, NotificationStatusEnum notificationStatusEnum) {
notificationTemplate = notificationTemplate.replace("${text}", text);
// 集数
double episode = 1.0;
if (ReUtil.contains(StringEnum.SEASON_REG, text)) {
@@ -39,13 +39,13 @@ public interface Message {
episodeFormat = episodeFormat + ".5";
}
messageTemplate = messageTemplate.replace("${episode}",
notificationTemplate = notificationTemplate.replace("${episode}",
NumberFormatUtil.format(episode, 1, 0)
);
messageTemplate = messageTemplate.replace("${episodeFormat}", episodeFormat);
notificationTemplate = notificationTemplate.replace("${episodeFormat}", episodeFormat);
if (Objects.isNull(ani)) {
return messageTemplate;
return notificationTemplate;
}
List<Func1<Ani, Object>> list = List.of(
@@ -64,38 +64,38 @@ public interface Message {
int season = ani.getSeason();
String seasonFormat = String.format("%02d", season);
messageTemplate = messageTemplate.replace("${seasonFormat}", seasonFormat);
notificationTemplate = notificationTemplate.replace("${seasonFormat}", seasonFormat);
messageTemplate = RenameUtil.replaceField(messageTemplate, ani, list);
notificationTemplate = RenameUtil.replaceField(notificationTemplate, ani, list);
String tmdbId = Optional.ofNullable(ani.getTmdb())
.map(Tmdb::getId)
.orElse("");
messageTemplate = messageTemplate.replace("${tmdbid}", tmdbId);
notificationTemplate = notificationTemplate.replace("${tmdbid}", tmdbId);
String tmdbUrl = "";
if (StrUtil.isNotBlank(tmdbId)) {
String type = ani.getOva() ? "movie" : "tv";
tmdbUrl = StrFormatter.format("https://www.themoviedb.org/{}/{}", type, tmdbId);
}
messageTemplate = messageTemplate.replace("${tmdburl}", tmdbUrl);
notificationTemplate = notificationTemplate.replace("${tmdburl}", tmdbUrl);
String emoji = messageEnum.getEmoji();
String action = messageEnum.getAction();
String emoji = notificationStatusEnum.getEmoji();
String action = notificationStatusEnum.getAction();
messageTemplate = messageTemplate.replace("${emoji}", emoji);
messageTemplate = messageTemplate.replace("${action}", action);
notificationTemplate = notificationTemplate.replace("${emoji}", emoji);
notificationTemplate = notificationTemplate.replace("${action}", action);
String downloadPath = FilePathUtil.getAbsolutePath(TorrentUtil.getDownloadPath(ani));
messageTemplate = messageTemplate.replace("${downloadPath}", downloadPath);
notificationTemplate = notificationTemplate.replace("${downloadPath}", downloadPath);
if (messageTemplate.contains("${jpTitle}")) {
if (notificationTemplate.contains("${jpTitle}")) {
String jpTitle = RenameUtil.getJpTitle(ani);
messageTemplate = messageTemplate.replace("${jpTitle}", jpTitle);
notificationTemplate = notificationTemplate.replace("${jpTitle}", jpTitle);
}
messageTemplate = RenameUtil.replaceEpisodeTitle(messageTemplate, episode, ani);
notificationTemplate = RenameUtil.replaceEpisodeTitle(notificationTemplate, episode, ani);
return messageTemplate.trim();
return notificationTemplate.trim();
}
}

View File

@@ -1,8 +1,8 @@
package ani.rss.msg;
package ani.rss.notification;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.enums.MessageEnum;
import ani.rss.entity.NotificationConfig;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.util.AfdianUtil;
import ani.rss.util.EmbyUtil;
import cn.hutool.core.lang.Assert;
@@ -12,17 +12,17 @@ import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class EmbyRefresh implements Message {
public class EmbyRefreshNotification implements BaseNotification {
@Override
public Boolean send(Config config, Ani ani, String text, MessageEnum messageEnum) {
public Boolean send(NotificationConfig notificationConfig, Ani ani, String text, NotificationStatusEnum notificationStatusEnum) {
Assert.isTrue(AfdianUtil.verifyExpirationTime(), "未解锁捐赠, 无法使用Emby媒体库刷新");
Long embyDelayed = config.getEmbyDelayed();
Long embyDelayed = notificationConfig.getEmbyDelayed();
if (embyDelayed > 0) {
ThreadUtil.sleep(embyDelayed, TimeUnit.SECONDS);
}
try {
EmbyUtil.refresh(config);
EmbyUtil.refresh(notificationConfig);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);

View File

@@ -0,0 +1,78 @@
package ani.rss.notification;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.entity.NotificationConfig;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.util.ConfigUtil;
import ani.rss.util.ExceptionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Objects;
/**
* 邮箱
*/
@Slf4j
public class MailNotification implements BaseNotification {
@Override
public Boolean send(NotificationConfig notificationConfig, Ani ani, String text, NotificationStatusEnum notificationStatusEnum) {
Config config = ConfigUtil.CONFIG;
String template = config.getNotificationTemplate();
template = replaceNotificationTemplate(ani, template, text, notificationStatusEnum);
String notificationTemplate = notificationConfig.getNotificationTemplate();
notificationTemplate = notificationTemplate.replace("${notification}", template);
notificationTemplate = replaceNotificationTemplate(ani, notificationTemplate, text, notificationStatusEnum);
String mailFrom = notificationConfig.getMailFrom();
String mailSMTPHost = notificationConfig.getMailSMTPHost();
String mailPassword = notificationConfig.getMailPassword();
Integer mailSMTPPort = notificationConfig.getMailSMTPPort();
String mailAddressee = notificationConfig.getMailAddressee();
Boolean mailImage = notificationConfig.getMailImage();
Boolean mailSSLEnable = notificationConfig.getMailSSLEnable();
Boolean mailTLSEnable = notificationConfig.getMailTLSEnable();
Assert.notBlank(mailFrom, "发件人邮箱 为空");
Assert.notBlank(mailSMTPHost, "SMTP地址 为空");
Assert.notBlank(mailPassword, "密码 为空");
Assert.notBlank(mailAddressee, "收件人 为空");
MailAccount mailAccount = new MailAccount()
.setUser(mailFrom)
.setFrom(StrFormatter.format("ani-rss <{}>", mailFrom))
.setPass(mailPassword)
.setHost(mailSMTPHost)
.setPort(mailSMTPPort)
.setSslEnable(mailSSLEnable)
.setStarttlsEnable(mailTLSEnable)
.setAuth(true);
notificationTemplate = notificationTemplate.replace("\n", "<br/>");
if (mailImage) {
String image = "https://docs.wushuo.top/null.png";
if (Objects.nonNull(ani)) {
image = ani.getImage();
}
notificationTemplate += StrFormatter.format("<br/><img src=\"{}\"/>", image);
}
try {
MailUtil.send(mailAccount, List.of(mailAddressee), text.length() > 200 ? ani.getTitle() : text, notificationTemplate, true);
return true;
} catch (Exception e) {
String message = ExceptionUtil.getMessage(e);
log.error(message, e);
return false;
}
}
}

View File

@@ -1,9 +1,11 @@
package ani.rss.msg;
package ani.rss.notification;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.enums.MessageEnum;
import ani.rss.entity.NotificationConfig;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.enums.ServerChanTypeEnum;
import ani.rss.util.ConfigUtil;
import ani.rss.util.GsonStatic;
import ani.rss.util.HttpReq;
import cn.hutool.core.text.StrFormatter;
@@ -18,52 +20,62 @@ import java.util.Objects;
* ServerChan
*/
@Slf4j
public class ServerChan implements Message {
public class ServerChanNotification implements BaseNotification {
private static final String MARKDOWN_STRING = "# <message>\n\n![<image>](<image>)";
@Override
public Boolean send(Config config, Ani ani, String text, MessageEnum messageEnum) {
text = replaceMessageTemplate(ani, config.getMessageTemplate(), text, messageEnum);
text = text.replace("\n", "\n\n");
String type = config.getServerChanType();
String sendKey = config.getServerChanSendKey();
String apiUrl = config.getServerChan3ApiUrl();
Boolean serverChanTitleAction = config.getServerChanTitleAction();
public Boolean send(NotificationConfig notificationConfig, Ani ani, String text, NotificationStatusEnum notificationStatusEnum) {
Config config = ConfigUtil.CONFIG;
String template = config.getNotificationTemplate();
template = replaceNotificationTemplate(ani, template, text, notificationStatusEnum);
String notificationTemplate = notificationConfig.getNotificationTemplate();
notificationTemplate = notificationTemplate.replace("${notification}", template);
notificationTemplate = replaceNotificationTemplate(ani, notificationTemplate, text, notificationStatusEnum);
notificationTemplate = notificationTemplate.replace("\n", "\n\n");
ServerChanTypeEnum type = notificationConfig.getServerChanType();
String sendKey = notificationConfig.getServerChanSendKey();
String apiUrl = notificationConfig.getServerChan3ApiUrl();
Boolean serverChanTitleAction = notificationConfig.getServerChanTitleAction();
Boolean flag = checkParam(type, sendKey, apiUrl);
if (!flag) {
return false;
}
String title = "";
String image = "https://docs.wushuo.top/null.png";
String title = text;
if (Objects.nonNull(ani)) {
if (StrUtil.isNotBlank(ani.getImage())) {
image = ani.getImage();
}
title = truncateMessage(ani.getTitle(), serverChanTitleAction ? 10 : 15);
if (serverChanTitleAction) {
String action = messageEnum.getAction();
title = StrFormatter.format("{}#{}", action, title);
String action = notificationStatusEnum.getAction();
title = StrFormatter.format("{}#{}", action, ani.getTitle());
}
}
String serverChanUrl = "";
String body = "";
String desp = MARKDOWN_STRING.replace("<message>", text).replace("<image>", image);
if (type.equals(ServerChanTypeEnum.SERVER_CHAN.getType())) {
String desp = MARKDOWN_STRING
.replace("<message>", notificationTemplate)
.replace("<image>", image);
if (type.equals(ServerChanTypeEnum.SERVER_CHAN)) {
serverChanUrl = ServerChanTypeEnum.SERVER_CHAN.getUrl().replace("<sendKey>", sendKey);
body = GsonStatic.toJson(Map.of(
"title", title,
"desp", desp
));
} else if (type.equals(ServerChanTypeEnum.SERVER_CHAN_3.getType())) {
}
if (type.equals(ServerChanTypeEnum.SERVER_CHAN_3)) {
serverChanUrl = apiUrl;
body = GsonStatic.toJson(Map.of(
"title", title,
"tags", "ass",
"tags", "ani-rss",
"desp", desp
));
}
@@ -73,27 +85,19 @@ public class ServerChan implements Message {
.thenFunction(HttpResponse::isOk);
}
private static Boolean checkParam(String type, String sendKey, String apiUrl) {
if (StrUtil.isBlank(type)) {
log.warn("server酱类型不能为空");
return false;
}
if (type.equals(ServerChanTypeEnum.SERVER_CHAN.getType())) {
private static Boolean checkParam(ServerChanTypeEnum type, String sendKey, String apiUrl) {
if (type.equals(ServerChanTypeEnum.SERVER_CHAN)) {
if (StrUtil.isBlank(sendKey)) {
log.warn("sendKey 不能为空");
return false;
}
} else if (type.equals(ServerChanTypeEnum.SERVER_CHAN_3.getType())) {
}
if (type.equals(ServerChanTypeEnum.SERVER_CHAN_3)) {
if (StrUtil.isBlank(apiUrl)) {
log.warn("apiUrl 不能为空");
return false;
}
} else {
log.warn("无效的server酱类型");
return false;
}
return true;
}

View File

@@ -0,0 +1,46 @@
package ani.rss.notification;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.entity.NotificationConfig;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.util.ConfigUtil;
import ani.rss.util.MenuUtil;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import java.util.Objects;
/**
* SystemMsg
*/
@Slf4j
public class SystemNotification implements BaseNotification {
@Override
public Boolean send(NotificationConfig notificationConfig, Ani ani, String text, NotificationStatusEnum notificationStatusEnum) {
Config config = ConfigUtil.CONFIG;
String template = config.getNotificationTemplate();
template = replaceNotificationTemplate(ani, template, text, notificationStatusEnum);
String notificationTemplate = notificationConfig.getNotificationTemplate();
notificationTemplate = notificationTemplate.replace("${notification}", template);
notificationTemplate = replaceNotificationTemplate(ani, notificationTemplate, text, notificationStatusEnum);
if (!SystemTray.isSupported()) {
log.error("SystemTray is not supported");
return false;
}
TrayIcon trayIcon = MenuUtil.trayIcon;
if (Objects.isNull(trayIcon)) {
log.error("未开启系统托盘 添加--gui参数启动");
return false;
}
TrayIcon.MessageType type = TrayIcon.MessageType.INFO;
if (Objects.nonNull(notificationStatusEnum)) {
if (notificationStatusEnum.name().equals(NotificationStatusEnum.ERROR.name())) {
type = TrayIcon.MessageType.ERROR;
}
}
trayIcon.displayMessage("ani-rss", notificationTemplate, type);
return true;
}
}

View File

@@ -1,8 +1,9 @@
package ani.rss.msg;
package ani.rss.notification;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.enums.MessageEnum;
import ani.rss.entity.NotificationConfig;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.util.ConfigUtil;
import ani.rss.util.GsonStatic;
import ani.rss.util.HttpReq;
@@ -25,13 +26,13 @@ import java.util.Optional;
* Telegram
*/
@Slf4j
public class Telegram implements Message {
public static synchronized Map<String, String> getUpdates(Config config) {
String telegramBotToken = config.getTelegramBotToken();
public class TelegramNotification implements BaseNotification {
public static synchronized Map<String, String> getUpdates(NotificationConfig notificationConfig) {
String telegramBotToken = notificationConfig.getTelegramBotToken();
if (StrUtil.isBlank(telegramBotToken)) {
return Map.of();
}
String telegramApiHost = config.getTelegramApiHost();
String telegramApiHost = notificationConfig.getTelegramApiHost();
telegramApiHost = StrUtil.blankToDefault(telegramApiHost, "https://api.telegram.org");
String url = StrFormatter.format("{}/bot{}/getUpdates", telegramApiHost, telegramBotToken);
Map<String, String> map = new HashMap<>();
@@ -73,14 +74,21 @@ public class Telegram implements Message {
return firstName + " " + lastName;
}
public Boolean send(Config config, Ani ani, String text, MessageEnum messageEnum) {
text = replaceMessageTemplate(ani, config.getMessageTemplate(), text, messageEnum);
String telegramBotToken = config.getTelegramBotToken();
String telegramChatId = config.getTelegramChatId();
Integer telegramTopicId = config.getTelegramTopicId();
String telegramApiHost = config.getTelegramApiHost();
Boolean telegramImage = config.getTelegramImage();
String telegramFormat = config.getTelegramFormat();
public Boolean send(NotificationConfig notificationConfig, Ani ani, String text, NotificationStatusEnum notificationStatusEnum) {
Config config = ConfigUtil.CONFIG;
String template = config.getNotificationTemplate();
template = replaceNotificationTemplate(ani, template, text, notificationStatusEnum);
String notificationTemplate = notificationConfig.getNotificationTemplate();
notificationTemplate = notificationTemplate.replace("${notification}", template);
notificationTemplate = replaceNotificationTemplate(ani, notificationTemplate, text, notificationStatusEnum);
String telegramBotToken = notificationConfig.getTelegramBotToken();
String telegramChatId = notificationConfig.getTelegramChatId();
Integer telegramTopicId = notificationConfig.getTelegramTopicId();
String telegramApiHost = notificationConfig.getTelegramApiHost();
Boolean telegramImage = notificationConfig.getTelegramImage();
String telegramFormat = notificationConfig.getTelegramFormat();
if (StrUtil.isBlank(telegramChatId) || StrUtil.isBlank(telegramBotToken)) {
log.warn("telegram 通知的参数不完整");
return false;
@@ -95,7 +103,7 @@ public class Telegram implements Message {
if (telegramTopicId > -1) {
body.put("message_thread_id", telegramTopicId);
}
body.put("text", text);
body.put("text", notificationTemplate);
if (StrUtil.isNotBlank(telegramFormat)) {
body.put("parse_mode", telegramFormat);
}
@@ -107,7 +115,7 @@ public class Telegram implements Message {
File configDir = ConfigUtil.getConfigDir();
File photo = new File(configDir + "/files/" + cover);
if (StrUtil.isBlank(cover) || !photo.exists()) {
return send(config, null, text, messageEnum);
return send(notificationConfig, null, notificationTemplate, notificationStatusEnum);
}
url = StrFormatter.format("{}/bot{}/sendPhoto", telegramApiHost, telegramBotToken);
@@ -115,7 +123,7 @@ public class Telegram implements Message {
HttpRequest request = HttpReq.post(url, true)
.contentType(ContentType.MULTIPART.getValue())
.form("chat_id", telegramChatId)
.form("caption", text)
.form("caption", notificationTemplate)
.form("photo", photo)
.form("parse_mode", telegramFormat);

View File

@@ -0,0 +1,58 @@
package ani.rss.notification;
import ani.rss.entity.Ani;
import ani.rss.entity.NotificationConfig;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.util.HttpReq;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
/**
* WebHook
*/
@Slf4j
public class WebHookNotification implements BaseNotification {
@Override
public Boolean send(NotificationConfig notificationConfig, Ani ani, String text, NotificationStatusEnum notificationStatusEnum) {
String notificationTemplate = notificationConfig.getNotificationTemplate();
notificationTemplate = notificationTemplate.replace("\n", "\\n");
notificationTemplate = replaceNotificationTemplate(ani, notificationTemplate, text, notificationStatusEnum);
String webHookMethod = notificationConfig.getWebHookMethod();
String webHookUrl = notificationConfig.getWebHookUrl();
String webHookBody = notificationConfig.getWebHookBody();
webHookUrl = replaceNotificationTemplate(ani, webHookUrl, text, notificationStatusEnum);
webHookBody = replaceNotificationTemplate(ani, webHookBody, text, notificationStatusEnum);
if (StrUtil.isBlank(webHookUrl)) {
log.warn("webhook url is blank");
return false;
}
webHookUrl = webHookUrl.replace("${notification}", notificationTemplate);
webHookBody = webHookBody.replace("${notification}", notificationTemplate);
String image = "https://docs.wushuo.top/null.png";
if (Objects.nonNull(ani) && StrUtil.isNotBlank(ani.getImage())) {
image = ani.getImage();
}
webHookUrl = webHookUrl.replace("${image}", image);
webHookBody = webHookBody.replace("${image}", image);
HttpRequest httpRequest = HttpReq.get(webHookUrl, true)
.method(Method.valueOf(webHookMethod));
if (StrUtil.isNotBlank(webHookBody)) {
httpRequest.body(webHookBody);
}
return httpRequest.thenFunction(HttpResponse::isOk);
}
}

View File

@@ -3,7 +3,7 @@ package ani.rss.util;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.entity.TorrentsInfo;
import ani.rss.enums.MessageEnum;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.enums.TorrentsTags;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Opt;
@@ -119,7 +119,7 @@ public class AlistUtil {
text = StrFormatter.format("已向alist添加上传任务 {}", fileName);
}
log.info(text);
MessageUtil.send(config, ani, text, MessageEnum.ALIST_UPLOAD);
NotificationUtil.send(config, ani, text, NotificationStatusEnum.ALIST_UPLOAD);
});
TorrentUtil.addTags(torrentsInfo, TorrentsTags.UPLOAD_COMPLETED.getValue());
return;
@@ -128,7 +128,7 @@ public class AlistUtil {
}
}
if (AfdianUtil.verifyExpirationTime()) {
MessageUtil.send(config, ani, "alist上传失败 " + fileName, MessageEnum.ERROR);
NotificationUtil.send(config, ani, "alist上传失败 " + fileName, NotificationStatusEnum.ERROR);
}
});
}

View File

@@ -48,7 +48,7 @@ public class AniUtil {
String s = FileUtil.readUtf8String(configFile);
List<Ani> anis = GsonStatic.fromJsonList(s, Ani.class);
for (Ani ani : anis) {
Ani newAni = Ani.bulidAni();
Ani newAni = Ani.createAni();
BeanUtil.copyProperties(ani, newAni, CopyOptions
.create()
.setIgnoreNullValue(true));
@@ -87,7 +87,7 @@ public class AniUtil {
type = StrUtil.blankToDefault(type, "mikan");
String subgroupId = MikanUtil.getSubgroupId(url);
Ani ani = Ani.bulidAni();
Ani ani = Ani.createAni();
ani.setUrl(url.trim());
if ("mikan".equals(type)) {

View File

@@ -2,9 +2,6 @@ package ani.rss.util;
import ani.rss.entity.Config;
import ani.rss.entity.Login;
import ani.rss.entity.MyMailAccount;
import ani.rss.enums.MessageEnum;
import ani.rss.enums.ServerChanTypeEnum;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.DynaBean;
import cn.hutool.core.bean.copier.CopyOptions;
@@ -56,7 +53,7 @@ public class ConfigUtil {
String password = Md5Util.digestHex("admin");
String messageTemplate = """
String notificationTemplate = """
${emoji}${emoji}${emoji}
事件类型: ${action}
标题: ${title}
@@ -117,18 +114,6 @@ public class ConfigUtil {
.setProxyUsername("")
.setProxyPassword("")
.setDownloadCount(0)
.setMail(false)
.setMailAddressee("")
.setMailImage(true)
.setMailAccount(
new MyMailAccount()
.setHost("")
.setPort(25)
.setFrom("")
.setPass("")
.setSslEnable(false)
.setStarttlsEnable(false)
)
.setLogin(new Login()
.setUsername("admin")
.setPassword(password)
@@ -138,14 +123,6 @@ public class ConfigUtil {
.setExclude(List.of("720[Pp]", "\\d-\\d", "合集", "特别篇"))
.setImportExclude(false)
.setEnabledExclude(false)
.setTelegram(false)
.setTelegramChatId("")
.setTelegramBotToken("")
.setTelegramApiHost("https://api.telegram.org")
.setTelegramImage(true)
.setTelegramTopicId(-1)
.setTelegramFormat("")
.setWebHook(false)
.setTmdb(false)
.setBgmJpName(false)
.setTmdbId(false)
@@ -153,9 +130,6 @@ public class ConfigUtil {
.setTmdbRomaji(false)
.setIpWhitelist(false)
.setIpWhitelistStr("")
.setWebHookBody("")
.setWebHookUrl("")
.setWebHookMethod("POST")
.setShowPlaylist(true)
.setOmit(true)
.setBgmToken("")
@@ -167,18 +141,7 @@ public class ConfigUtil {
.setRenameTemplate("[${subgroup}] ${title} S${seasonFormat}E${episodeFormat}")
.setRenameDelYear(false)
.setRenameDelTmdbId(false)
.setMessageList(List.of(
MessageEnum.DOWNLOAD_START,
MessageEnum.OMIT,
MessageEnum.ERROR
))
.setVerifyLoginIp(false)
.setServerChan(false)
.setServerChanType(ServerChanTypeEnum.SERVER_CHAN.getType())
.setServerChanSendKey("")
.setServerChan3ApiUrl("")
.setServerChanTitleAction(true)
.setSystemMsg(false)
.setAutoTrackersUpdate(false)
.setTrackersUpdateUrls("https://cf.trackerslist.com/best.txt")
.setAutoUpdate(false)
@@ -204,10 +167,6 @@ public class ConfigUtil {
.setOutTradeNo("")
.setTryOut(false)
.setVerifyExpirationTime(false)
.setEmbyRefresh(false)
.setEmbyApiKey("")
.setEmbyRefreshViewIds(new ArrayList<>())
.setEmbyDelayed(0L)
.setProcrastinating(false)
.setProcrastinatingDay(14)
.setGithub("None")
@@ -225,7 +184,8 @@ public class ConfigUtil {
.setShowLastDownloadTime(false)
.setCompleted(false)
.setCompletedPathTemplate(completedPathTemplate)
.setMessageTemplate(messageTemplate);
.setNotificationTemplate(notificationTemplate)
.setNotificationConfigList(new ArrayList<>());
}
/**
@@ -397,6 +357,9 @@ public class ConfigUtil {
public static void format(Config config) {
formatPath(config);
formatUrl(config);
String messageTemplate = config.getNotificationTemplate();
config.setNotificationTemplate(messageTemplate.trim());
}
/**
@@ -408,11 +371,9 @@ public class ConfigUtil {
List<Func1<Config, String>> func1List = List.of(
Config::getDownloadToolHost,
Config::getAlistHost,
Config::getTelegramApiHost,
Config::getMikanHost,
Config::getTmdbApi,
Config::getCustomGithubUrl,
Config::getEmbyHost
Config::getCustomGithubUrl
);
DynaBean dynaBean = DynaBean.create(config);

View File

@@ -1,7 +1,7 @@
package ani.rss.util;
import ani.rss.entity.Config;
import ani.rss.entity.EmbyViews;
import ani.rss.entity.NotificationConfig;
import cn.hutool.core.lang.Assert;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@@ -17,18 +17,18 @@ public class EmbyUtil {
/**
* 扫描媒体库
*/
public static synchronized void refresh(Config config) {
List<String> viewIds = config.getEmbyRefreshViewIds();
List<EmbyViews> views = getViews(config);
public static synchronized void refresh(NotificationConfig notificationConfig) {
List<String> viewIds = notificationConfig.getEmbyRefreshViewIds();
List<EmbyViews> views = getViews(notificationConfig);
List<String> newViewIds = views
.stream()
.filter(view -> viewIds.contains(view.getId()))
.peek(view -> refresh(view, config))
.peek(view -> refresh(view, notificationConfig))
.map(EmbyViews::getId)
.toList();
config.setEmbyRefreshViewIds(newViewIds);
notificationConfig.setEmbyRefreshViewIds(newViewIds);
}
/**
@@ -36,9 +36,9 @@ public class EmbyUtil {
*
* @param embyViews 媒体库
*/
public static synchronized void refresh(EmbyViews embyViews, Config config) {
String embyHost = config.getEmbyHost();
String embyApiKey = config.getEmbyApiKey();
public static synchronized void refresh(EmbyViews embyViews, NotificationConfig notificationConfig) {
String embyHost = notificationConfig.getEmbyHost();
String embyApiKey = notificationConfig.getEmbyApiKey();
Assert.notBlank(embyHost, "embyHost 为空");
Assert.notBlank(embyApiKey, "embyApiKey 为空");
@@ -63,9 +63,9 @@ public class EmbyUtil {
*
* @return 媒体库列表
*/
public static synchronized List<EmbyViews> getViews(Config config) {
String embyHost = config.getEmbyHost();
String embyApiKey = config.getEmbyApiKey();
public static synchronized List<EmbyViews> getViews(NotificationConfig notificationConfig) {
String embyHost = notificationConfig.getEmbyHost();
String embyApiKey = notificationConfig.getEmbyApiKey();
Assert.notBlank(embyHost, "embyHost 为空");
Assert.notBlank(embyApiKey, "embyApiKey 为空");

View File

@@ -2,7 +2,6 @@ package ani.rss.util;
import ani.rss.entity.Config;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;

View File

@@ -3,7 +3,7 @@ package ani.rss.util;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.entity.Item;
import ani.rss.enums.MessageEnum;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.enums.StringEnum;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
@@ -330,7 +330,7 @@ public class ItemsUtil {
return;
}
MessageUtil.send(config, ani, CollUtil.join(sList, "\n"), MessageEnum.OMIT);
NotificationUtil.send(config, ani, CollUtil.join(sList, "\n"), NotificationStatusEnum.OMIT);
}
public static int currentEpisodeNumber(Ani ani, List<Item> items) {
@@ -419,7 +419,7 @@ public class ItemsUtil {
}
MyCacheUtil.put(key, text, TimeUnit.DAYS.toMillis(1));
MessageUtil.send(config, ani, text, MessageEnum.PROCRASTINATING);
NotificationUtil.send(config, ani, text, NotificationStatusEnum.PROCRASTINATING);
});
}

View File

@@ -1,84 +0,0 @@
package ani.rss.util;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.enums.MessageEnum;
import ani.rss.msg.Message;
import cn.hutool.core.bean.DynaBean;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.thread.ExecutorBuilder;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
@Slf4j
public class MessageUtil {
private static final ExecutorService EXECUTOR_SERVICE = ExecutorBuilder.create()
.setCorePoolSize(1)
.setMaxPoolSize(1)
.setWorkQueue(new LinkedBlockingQueue<>(256))
.build();
public static synchronized void send(Config config, Ani ani, String text, MessageEnum messageEnum) {
List<MessageEnum> messageList = config.getMessageList();
if (!messageList.contains(messageEnum)) {
return;
}
if (!AfdianUtil.verifyExpirationTime()) {
if (MessageEnum.COMPLETED == messageEnum) {
log.warn("未解锁捐赠, 无法使用订阅完结通知");
return;
}
if (MessageEnum.ALIST_UPLOAD == messageEnum) {
log.warn("未解锁捐赠, 无法使用Alist上传通知");
return;
}
}
Boolean isMessage = Opt.ofNullable(ani)
.map(Ani::getMessage)
.orElse(true);
if (!isMessage) {
// 未开启此订阅通知
return;
}
Set<Class<?>> classes = ClassUtil.scanPackage("ani.rss.msg");
DynaBean dynaBean = DynaBean.create(config);
for (Class<?> aClass : classes) {
if (aClass.isInterface()) {
continue;
}
String name = aClass.getSimpleName();
name = name.substring(0, 1).toLowerCase() + name.substring(1);
Object b = dynaBean.get(name);
if (Objects.isNull(b)) {
continue;
}
if (!(b instanceof Boolean)) {
continue;
}
if (!(Boolean) b) {
continue;
}
Message message = (Message) ReflectUtil.newInstance(aClass);
EXECUTOR_SERVICE.execute(() -> {
try {
message.send(config, ani, text, messageEnum);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
});
}
}
}

View File

@@ -0,0 +1,68 @@
package ani.rss.util;
import ani.rss.entity.Ani;
import ani.rss.entity.Config;
import ani.rss.entity.NotificationConfig;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.enums.NotificationTypeEnum;
import ani.rss.notification.BaseNotification;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.thread.ExecutorBuilder;
import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
@Slf4j
public class NotificationUtil {
private static final ExecutorService EXECUTOR_SERVICE = ExecutorBuilder.create()
.setCorePoolSize(1)
.setMaxPoolSize(1)
.setWorkQueue(new LinkedBlockingQueue<>(256))
.build();
public static synchronized void send(Config config, Ani ani, String text, NotificationStatusEnum notificationStatusEnum) {
Boolean isMessage = Opt.ofNullable(ani)
.map(Ani::getMessage)
.orElse(true);
if (!isMessage) {
// 未开启此订阅通知
return;
}
List<NotificationConfig> notificationConfigList = config.getNotificationConfigList();
for (NotificationConfig notificationConfig : notificationConfigList) {
Boolean enable = notificationConfig.getEnable();
NotificationTypeEnum notificationType = notificationConfig.getNotificationType();
if (!enable) {
// 未开启
continue;
}
if (!AfdianUtil.verifyExpirationTime()) {
if (NotificationStatusEnum.COMPLETED == notificationStatusEnum) {
log.warn("未解锁捐赠, 无法使用订阅完结通知");
continue;
}
if (NotificationStatusEnum.ALIST_UPLOAD == notificationStatusEnum) {
log.warn("未解锁捐赠, 无法使用Alist上传通知");
continue;
}
}
BaseNotification baseNotification = ReflectUtil.newInstance(notificationType.getAClass());
EXECUTOR_SERVICE.execute(() -> {
try {
baseNotification.send(notificationConfig, ani, text, notificationStatusEnum);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
});
}
}
}

View File

@@ -3,7 +3,7 @@ package ani.rss.util;
import ani.rss.action.ClearCacheAction;
import ani.rss.download.BaseDownload;
import ani.rss.entity.*;
import ani.rss.enums.MessageEnum;
import ani.rss.enums.NotificationStatusEnum;
import ani.rss.enums.StringEnum;
import ani.rss.enums.TorrentsTags;
import cn.hutool.core.date.DateField;
@@ -260,7 +260,7 @@ public class TorrentUtil {
}
if (currentDownloadCount >= totalEpisodeNumber) {
log.info("{} 第 {} 季 共 {} 集 已全部下载完成, 自动停止订阅", title, season, totalEpisodeNumber);
MessageUtil.send(config, ani, StrFormatter.format("{} 订阅已完结", title), MessageEnum.COMPLETED);
NotificationUtil.send(config, ani, StrFormatter.format("{} 订阅已完结", title), NotificationStatusEnum.COMPLETED);
ani.setEnable(false);
AniUtil.sync();
}
@@ -693,7 +693,7 @@ public class TorrentUtil {
if (!master) {
text = StrFormatter.format("(备用RSS) {}", text);
}
MessageUtil.send(ConfigUtil.CONFIG, ani, text, MessageEnum.DOWNLOAD_START);
NotificationUtil.send(ConfigUtil.CONFIG, ani, text, NotificationStatusEnum.DOWNLOAD_START);
try {
createTvShowNfo(savePath, ani);
@@ -716,9 +716,9 @@ public class TorrentUtil {
log.error("{} 下载失败将进行重试, 当前重试次数为{}次", name, i);
}
log.error("{} 添加失败,疑似为坏种", name);
MessageUtil.send(ConfigUtil.CONFIG, ani,
NotificationUtil.send(ConfigUtil.CONFIG, ani,
StrFormatter.format("{} 添加失败,疑似为坏种", name),
MessageEnum.ERROR);
NotificationStatusEnum.ERROR);
}
/**
@@ -864,7 +864,7 @@ public class TorrentUtil {
if (tags.contains(TorrentsTags.BACK_RSS.getValue())) {
text = StrFormatter.format("(备用RSS) {}", text);
}
MessageUtil.send(ConfigUtil.CONFIG, ani, text, MessageEnum.DOWNLOAD_END);
NotificationUtil.send(ConfigUtil.CONFIG, ani, text, NotificationStatusEnum.DOWNLOAD_END);
if (Objects.isNull(ani)) {
return;

View File

@@ -1,360 +0,0 @@
<template>
<el-collapse v-model="messageActiveName" accordion>
<el-collapse-item title="通知设置" name="0">
<div style="margin-bottom: 12px;">
<div style="margin-bottom: 12px;margin-top: 4px;">
<el-input v-model:model-value="props.config.messageTemplate" type="textarea"
placeholder="${text}" :autosize="{ minRows: 2}"/>
<div class="flex" style="width: 100%;justify-content: end;">
<a target="_blank" href="https://docs.wushuo.top/config/message">通知模版示例</a>
</div>
</div>
<div>
<el-checkbox-group v-model:model-value="config.messageList">
<el-checkbox label="开始下载" value="DOWNLOAD_START"/>
<el-checkbox label="下载完成" value="DOWNLOAD_END"/>
<el-checkbox label="缺集" value="OMIT"/>
<el-checkbox label="错误" value="ERROR"/>
<el-checkbox :disabled="!props.config['verifyExpirationTime']" label="Alist上传通知" value="ALIST_UPLOAD"/>
<el-checkbox :disabled="!props.config['verifyExpirationTime']" label="订阅完结" value="COMPLETED"/>
<el-checkbox :disabled="!props.config['verifyExpirationTime']" label="摸鱼检测" value="PROCRASTINATING"/>
</el-checkbox-group>
</div>
<div>
<el-text class="mx-1" size="small">
下载完成通知暂不支持 Aria2Alist
</el-text>
<br>
<AfdianPrompt :config="props.config" name="Alist上传通知、订阅完结、摸鱼检测"/>
</div>
</div>
</el-collapse-item>
<el-collapse-item title="TG通知" name="1">
<el-form label-width="auto" @submit="(event)=>{
event.preventDefault()
}">
<el-form-item label="Api Host">
<el-input v-model:model-value="props.config.telegramApiHost"
:disabled="config.telegram"
placeholder="https://api.telegram.org"/>
</el-form-item>
<el-form-item label="Token">
<el-input v-model:model-value="props.config.telegramBotToken" :disabled="config.telegram"
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"/>
</el-form-item>
<el-form-item label="ChatId">
<div>
<div style="justify-content: space-between;width: 100%;" class="auto">
<div style="margin-top: 4px;margin-right: 4px;">
<el-input v-model:model-value="props.config.telegramChatId" :disabled="config.telegram"
placeholder="123456789"/>
</div>
<div class="flex" style="margin-top: 4px;align-items: center;">
<div>
<el-select v-model:model-value="chatId" @change="chatIdChange" style="width: 160px"
:disabled="config.telegram">
<el-option v-for="item in Object.keys(chatIdMap)"
:key="item"
:label="item"
:value="item"/>
</el-select>
</div>
<div style="margin-left: 4px;">
<el-button icon="Refresh" bg text @click="getUpdates" :loading="getUpdatesLoading"
:disabled="config.telegram"/>
</div>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="话题ID">
<el-input-number v-model="props.config.telegramTopicId" :disabled="config.telegram"
:min="-1" style="width: 160px;"/>
</el-form-item>
<el-form-item label="图片">
<el-switch v-model:model-value="props.config.telegramImage" :disabled="config.telegram"/>
</el-form-item>
<el-form-item label="格式">
<div style="width: 160px;">
<el-select v-model:model-value="props.config.telegramFormat" :disabled="config.telegram" placeholder="None">
<el-option label="None" value=""/>
<el-option label="Markdown" value="Markdown"/>
<el-option label="HTML" value="HTML"/>
</el-select>
</div>
</el-form-item>
<el-form-item label="开关">
<div style="width: 100%;display: flex;justify-content: space-between;">
<el-switch v-model:model-value="props.config.telegram"/>
<el-button bg text @click="messageTest('Telegram')"
:loading="messageTestLoading && messageTestType === 'Telegram'"
:disabled="!config.telegram" icon="Odometer">测试
</el-button>
</div>
</el-form-item>
</el-form>
</el-collapse-item>
<el-collapse-item title="邮箱通知" name="2">
<el-form label-width="auto" @submit="(event)=>{
event.preventDefault()
}">
<el-form-item label="SMTP地址">
<el-input v-model:model-value="props.config.mailAccount.host" :disabled="config.mail"
placeholder="smtp.xx.com"/>
</el-form-item>
<el-form-item label="SMTP端口">
<el-input-number v-model:model-value="props.config.mailAccount.port" :min="1" :max="65535"
:disabled="config.mail"/>
</el-form-item>
<el-form-item label="发件人邮箱">
<el-input v-model:model-value="props.config.mailAccount.from" :disabled="config.mail"
placeholder="xx@xx.com"/>
</el-form-item>
<el-form-item label="密码">
<el-input v-model:model-value="props.config.mailAccount.pass" show-password :disabled="config.mail"/>
</el-form-item>
<el-form-item label="SSL">
<el-switch v-model:model-value="props.config.mailAccount.sslEnable" :disabled="config.mail"/>
</el-form-item>
<el-form-item label="STARTTLS">
<el-switch v-model:model-value="props.config.mailAccount['starttlsEnable']" :disabled="config.mail"/>
</el-form-item>
<el-form-item label="收件人邮箱">
<el-input v-model:model-value="props.config.mailAddressee" :disabled="config.mail"
placeholder="xx@xx.com"></el-input>
</el-form-item>
<el-form-item label="图片">
<el-switch v-model:model-value="props.config.mailImage" :disabled="config.mail"/>
</el-form-item>
<el-form-item label="开关">
<div style="width: 100%;display: flex;justify-content: space-between;">
<el-switch v-model:model-value="props.config.mail"></el-switch>
<el-button bg text @click="messageTest('Mail')" :loading="messageTestLoading && messageTestType === 'Mail'"
:disabled="!config.mail" icon="Odometer">测试
</el-button>
</div>
</el-form-item>
</el-form>
</el-collapse-item>
<el-collapse-item title="Server酱" name="3">
<el-form label-width="auto" @submit="(event)=>{
event.preventDefault()
}">
<el-form-item label="Type">
<el-select v-model="props.config.serverChanType" :disabled="config.serverChan">
<el-option
v-for="(value, label) in { 'server酱': 'serverChan', 'server酱³': 'serverChan3' }"
:key="label"
:label="label"
:value="value"
/>
</el-select>
</el-form-item>
<el-form-item label="sendKey" v-if="props.config.serverChanType === 'serverChan'">
<el-input v-model="props.config.serverChanSendKey" placeholder="1234567890"
:disabled="config.serverChan"></el-input>
</el-form-item>
<el-form-item label="apiUrl" v-else-if="props.config.serverChanType === 'serverChan3'">
<el-input v-model="props.config.serverChan3ApiUrl"
:disabled="config.serverChan"
placeholder="https://<uid>.push.ft07.com/send/<sendKey>.send"></el-input>
</el-form-item>
<el-form-item label="事件标题">
<el-switch v-model="props.config['serverChanTitleAction']"
:disabled="!config.serverChan"/>
</el-form-item>
<el-form-item label="开关">
<div style="display: flex;width: 100%;justify-content: space-between;">
<el-switch v-model:model-value="props.config.serverChan"/>
<el-button bg text @click="messageTest('ServerChan')"
:loading="messageTestLoading && messageTestType === 'ServerChan'"
:disabled="!config.serverChan" icon="Odometer">测试
</el-button>
</div>
</el-form-item>
</el-form>
</el-collapse-item>
<el-collapse-item name="4" title="系统通知">
<el-form label-width="auto" @submit="(event)=>{
event.preventDefault()
}">
<el-form-item label="开关">
<div style="display: flex;width: 100%;justify-content: space-between;">
<el-switch v-model:model-value="props.config.systemMsg"/>
<el-button :disabled="!config.systemMsg" :loading="messageTestLoading" bg
icon="Odometer"
text @click="messageTest('SystemMsg')">测试
</el-button>
</div>
</el-form-item>
</el-form>
</el-collapse-item>
<el-collapse-item title="WebHook" name="5">
<el-form label-width="auto" @submit="(event)=>{
event.preventDefault()
}">
<el-form-item label="Method">
<el-select v-model:model-value="props.config.webHookMethod">
<el-option v-for="item in ['POST','GET','PUT','DELETE']"
:key="item"
:label="item"
:value="item"/>
</el-select>
</el-form-item>
<el-form-item label="URL">
<el-input v-model:model-value="props.config.webHookUrl" type="textarea"
autosize
placeholder="http://www.xxx.com?text=test_${message}"></el-input>
</el-form-item>
<el-form-item label="Body">
<el-input v-model:model-value="props.config.webHookBody" type="textarea"
:autosize="{ minRows: 2}"
placeholder='{"text":"test_${message}"}'></el-input>
</el-form-item>
<el-form-item label="开关">
<div style="display: flex;width: 100%;justify-content: space-between;">
<el-switch v-model:model-value="props.config.webHook"/>
<el-button bg text @click="messageTest('WebHook')"
:loading="messageTestLoading && messageTestType === 'WebHook'"
:disabled="!config.webHook" icon="Odometer">测试
</el-button>
</div>
</el-form-item>
<div style="display: flex;justify-content: end;">
<a target="_blank" href="https://docs.wushuo.top/config/message">通知模版示例</a>
</div>
</el-form>
</el-collapse-item>
<el-collapse-item name="6" title="Emby媒体库刷新">
<el-form label-width="auto" @submit="(event)=>{
event.preventDefault()
}">
<el-form-item label="EmbyHost">
<el-input v-model="props.config['embyHost']" placeholder="http://x.x.x.x:8096"/>
</el-form-item>
<el-form-item label="Emby密钥">
<el-input v-model="props.config['embyApiKey']"/>
</el-form-item>
<el-form-item label="媒体库">
<div>
<el-checkbox-group v-model="props.config['embyRefreshViewIds']">
<el-checkbox
v-for="view in views"
:key="view.id"
:label="view.name"
:value="view.id"/>
</el-checkbox-group>
<div>
<el-button :loading="getEmbyViewsLoading" bg icon="Refresh" text @click="getEmbyViews"/>
</div>
</div>
</el-form-item>
<el-form-item label="延迟">
<el-input-number v-model="props.config['embyDelayed']" :min="0">
<template #suffix>
<span></span>
</template>
</el-input-number>
</el-form-item>
<el-form-item label="开启">
<el-switch v-model="props.config['embyRefresh']" :disabled="!props.config['verifyExpirationTime']"/>
</el-form-item>
<div class="flex" style="justify-content: space-between;width: 100%;">
<AfdianPrompt :config="props.config" name="Emby媒体库刷新"/>
<el-button :loading="messageTestLoading" bg
icon="Odometer"
text @click="messageTest('EmbyRefresh')">测试
</el-button>
</div>
</el-form>
</el-collapse-item>
</el-collapse>
</template>
<script setup>
import {onMounted, ref} from "vue";
import {ElMessage} from "element-plus";
import api from "../js/api.js";
import AfdianPrompt from "../other/AfdianPrompt.vue";
const chatIdMap = ref({})
const chatId = ref('')
const getUpdatesLoading = ref(false)
const views = ref([])
const getEmbyViewsLoading = ref(false)
const getEmbyViews = () => {
getEmbyViewsLoading.value = true
api.post('api/emby?type=getViews', props.config)
.then(res => {
views.value = res.data
})
.finally(() => {
getEmbyViewsLoading.value = false
})
}
onMounted(() => {
if (props.config['embyHost'] && props.config['embyApiKey']) {
getEmbyViews()
}
})
const getUpdates = () => {
if (!props.config.telegramBotToken.length) {
ElMessage.error('Token 不能为空')
return
}
getUpdatesLoading.value = true
api.post("api/telegram?method=getUpdates", props.config)
.then(res => {
chatIdMap.value = res.data
if (Object.keys(chatIdMap.value).length) {
chatId.value = Object.keys(chatIdMap.value)[0]
chatIdChange(chatId.value)
}
})
.finally(() => {
getUpdatesLoading.value = false
})
}
const chatIdChange = (k) => {
props.config.telegramChatId = chatIdMap.value[k]
}
const messageTestLoading = ref(false)
const messageTestType = ref('')
const messageTest = (type) => {
messageTestType.value = type
messageTestLoading.value = true
let config = JSON.parse(JSON.stringify(props.config))
config.embyDelayed = 0
api.post("api/message?type=" + type, config)
.then(res => {
ElMessage.success(res.message)
})
.finally(() => {
messageTestLoading.value = false
})
}
const messageActiveName = ref('0')
let props = defineProps(['config'])
</script>
<style>
@media (min-width: 1000px) {
.auto {
display: flex;
}
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div style="margin-bottom: 12px;margin-top: 4px;">
<el-input v-model:model-value="props.config['notificationTemplate']" type="textarea"
placeholder="${text}" :autosize="{ minRows: 2}"/>
<div class="flex" style="width: 100%;justify-content: end;">
<a target="_blank" href="https://docs.wushuo.top/config/message">通知模版示例</a>
</div>
</div>
<div>
<div>
<el-space wrap class="flex flex-wrap gap-4" size="small">
<el-card v-for="it in props.config['notificationConfigList']" shadow="never" style="min-width: 240px">
<div style="align-items: center;justify-content: space-between;" class="flex">
<div>
<p>
{{ getLabel(it['notificationType']) }}
</p>
<el-text size="small">
{{ it['comment'] ? it['comment'] : '无备注' }}
</el-text>
</div>
<div>
<el-button icon="Edit" bg text type="primary" @click="notificationConfigRef?.show(it)"/>
<el-button icon="Delete" bg text type="danger" @click="del(it)"/>
</div>
</div>
</el-card>
</el-space>
</div>
<el-button
icon="FolderAdd"
bg text
type="primary"
@click="add"
style="margin-top: 12px;"
>
添加通知类型
</el-button>
</div>
<NotificationConfig ref="notificationConfigRef" v-model:config="props.config"/>
</template>
<script setup>
import NotificationConfig from "./NotificationConfig.vue";
import {ref} from "vue";
import {getLabel} from "../js/notification-type.js";
import api from "../js/api.js";
let add = () => {
api.post('api/notification?type=add')
.then((res) => {
props.config['notificationConfigList'].push(res.data)
notificationConfigRef.value?.show(res.data)
})
}
let del = (it) => {
props.config['notificationConfigList'] = props.config['notificationConfigList'].filter(item => item !== it)
}
let notificationConfigRef = ref()
let props = defineProps(['config'])
</script>
<style>
@media (min-width: 1000px) {
.auto {
display: flex;
}
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<el-dialog v-model="dialogVisible" center title="修改通知">
<el-scrollbar style="height: 400px;padding: 15px;">
<el-form label-width="auto">
<el-form-item label="通知类型">
<el-select v-model="notificationConfig['notificationType']">
<el-option :label="it.label" :value="it.name" :key="it.name" v-for="it in notificationTypeList"/>
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="notificationConfig['comment']" placeholder="无备注"/>
</el-form-item>
<el-form-item label="通知状态">
<el-checkbox-group v-model:model-value="notificationConfig['statusList']">
<el-checkbox label="开始下载" value="DOWNLOAD_START"/>
<el-checkbox label="下载完成" value="DOWNLOAD_END"/>
<el-checkbox label="缺集" value="OMIT"/>
<el-checkbox label="错误" value="ERROR"/>
<el-checkbox :disabled="!props.config['verifyExpirationTime']" label="Alist上传通知" value="ALIST_UPLOAD"/>
<el-checkbox :disabled="!props.config['verifyExpirationTime']" label="订阅完结" value="COMPLETED"/>
<el-checkbox :disabled="!props.config['verifyExpirationTime']" label="摸鱼检测" value="PROCRASTINATING"/>
</el-checkbox-group>
<AfdianPrompt :config="props.config" name="Alist上传通知、订阅完结、摸鱼检测"/>
</el-form-item>
<el-form-item label="通知模版">
<el-input v-model:model-value="notificationConfig['notificationTemplate']" type="textarea"
placeholder="${text}" :autosize="{ minRows: 2}"/>
</el-form-item>
<EmbyRefreshNotification v-model:notification-config="notificationConfig" v-model:config="props.config"/>
<MailNotification v-model:notification-config="notificationConfig" v-model:config="props.config"/>
<ServerChanNotification v-model:notification-config="notificationConfig" v-model:config="props.config"/>
<TelegramNotification v-model:notification-config="notificationConfig" v-model:config="props.config"/>
<WebhookNotification v-model:notification-config="notificationConfig" v-model:config="props.config"/>
</el-form>
</el-scrollbar>
<div class="flex" style="justify-content: space-between;width: 100%;margin-top: 8px;">
<el-button bg text @click="messageTest" icon="Odometer" :loading="messageTestLoading">测试
</el-button>
<el-button @click="dialogVisible = false" text bg icon="Check" type="primary">确定
</el-button>
</div>
</el-dialog>
</template>
<script setup>
import {ref} from "vue";
import AfdianPrompt from "../other/AfdianPrompt.vue";
import EmbyRefreshNotification from "./notification/EmbyRefreshNotification.vue";
import MailNotification from "./notification/MailNotification.vue";
import ServerChanNotification from "./notification/ServerChanNotification.vue";
import TelegramNotification from "./notification/TelegramNotification.vue";
import WebhookNotification from "./notification/WebhookNotification.vue";
import {notificationTypeList} from "../js/notification-type.js";
import {ElMessage} from "element-plus";
import api from "../js/api.js";
let notificationConfig = ref({
"comment": "无备注",
"notificationTemplate": "${notification}",
"notificationType": "TELEGRAM",
"mailSMTPHost": "smtp.qq.com",
"mailSMTPPort": 465,
"mailFrom": "",
"mailPassword": "",
"mailSSLEnable": true,
"mailTLSEnable": false,
"mailAddressee": "",
"mailImage": true,
"serverChanType": "SERVER_CHAN",
"serverChanSendKey": "",
"serverChan3ApiUrl": "",
"serverChanTitleAction": true,
"telegramBotToken": "",
"telegramChatId": "",
"telegramTopicId": -1,
"telegramApiHost": "https://api.telegram.org",
"telegramImage": true,
"telegramFormat": "",
"webHookMethod": "POST",
"webHookUrl": "",
"webHookBody": "",
"embyRefresh": false,
"embyApiKey": "",
"embyRefreshViewIds": [],
"embyDelayed": 0,
"statusList": [
"DOWNLOAD_START",
"OMIT",
"ERROR"
]
})
const messageTestLoading = ref(false)
const messageTest = () => {
messageTestLoading.value = true
let config = JSON.parse(JSON.stringify(notificationConfig.value))
config.embyDelayed = 0
api.post("api/notification?type=test", config)
.then(res => {
ElMessage.success(res.message)
})
.finally(() => {
messageTestLoading.value = false
})
}
let dialogVisible = ref(false)
let props = defineProps(['config'])
let show = (newNotificationConfig) => {
notificationConfig.value = newNotificationConfig
dialogVisible.value = true
}
defineExpose({
show
})
</script>

View File

@@ -0,0 +1,66 @@
<template>
<template v-if="props.notificationConfig['notificationType'] === 'EMBY_REFRESH'">
<el-form-item label="EmbyHost">
<el-input v-model="props.notificationConfig['embyHost']" placeholder="http://x.x.x.x:8096"/>
</el-form-item>
<el-form-item label="Emby密钥">
<el-input v-model="props.notificationConfig['embyApiKey']"/>
</el-form-item>
<el-form-item label="媒体库">
<div>
<el-checkbox-group v-model="props.notificationConfig['embyRefreshViewIds']">
<el-checkbox
v-for="view in views"
:key="view.id"
:label="view.name"
:value="view.id"/>
</el-checkbox-group>
<div>
<el-button :loading="getEmbyViewsLoading" bg icon="Refresh" text @click="getEmbyViews"/>
</div>
</div>
</el-form-item>
<el-form-item label="延迟">
<el-input-number v-model="props.notificationConfig['embyDelayed']" :min="0">
<template #suffix>
<span></span>
</template>
</el-input-number>
</el-form-item>
<el-form-item label="开启">
<el-switch v-model="props.notificationConfig['enable']" :disabled="!props.config['verifyExpirationTime']"/>
</el-form-item>
<div class="flex" style="justify-content: space-between;width: 100%;">
<AfdianPrompt :config="props.config" name="Emby媒体库刷新"/>
</div>
</template>
</template>
<script setup>
import AfdianPrompt from "../../other/AfdianPrompt.vue";
import {onMounted, ref} from "vue";
import api from "../../js/api.js";
const views = ref([])
const getEmbyViewsLoading = ref(false)
const getEmbyViews = () => {
getEmbyViewsLoading.value = true
api.post('api/emby?type=getViews', props.config)
.then(res => {
views.value = res.data
})
.finally(() => {
getEmbyViewsLoading.value = false
})
}
onMounted(() => {
if (props.notificationConfig['embyHost'] && props.notificationConfig['embyApiKey']) {
getEmbyViews()
}
})
let props = defineProps(['notificationConfig', 'config'])
</script>

View File

@@ -0,0 +1,34 @@
<template>
<template v-if="props.notificationConfig['notificationType'] === 'MAIL'">
<el-form-item label="SMTP地址">
<el-input v-model:model-value="props.notificationConfig['mailSMTPHost']" placeholder="smtp.qq.com"/>
</el-form-item>
<el-form-item label="SMTP端口">
<el-input-number v-model:model-value="props.notificationConfig['mailSMTPPort']" :min="1" :max="65535"/>
</el-form-item>
<el-form-item label="发件人邮箱">
<el-input v-model:model-value="props.notificationConfig['mailFrom']"
placeholder="xx@qq.com"/>
</el-form-item>
<el-form-item label="密码">
<el-input v-model:model-value="props.notificationConfig['mailPassword']" show-password/>
</el-form-item>
<el-form-item label="SSL">
<el-switch v-model:model-value="props.notificationConfig['mailSSLEnable']"/>
</el-form-item>
<el-form-item label="STARTTLS">
<el-switch v-model:model-value="props.notificationConfig['mailTLSEnable']"/>
</el-form-item>
<el-form-item label="收件人邮箱">
<el-input v-model:model-value="props.notificationConfig['mailAddressee']"
placeholder="xx@xx.com"></el-input>
</el-form-item>
<el-form-item label="图片">
<el-switch v-model:model-value="props.notificationConfig['mailImage']"/>
</el-form-item>
</template>
</template>
<script setup>
let props = defineProps(['notificationConfig', 'config'])
</script>

View File

@@ -0,0 +1,28 @@
<template>
<template v-if="props.notificationConfig['notificationType'] === 'SERVER_CHAN'">
<el-form-item label="Type">
<el-select v-model="props.notificationConfig.serverChanType">
<el-option
v-for="it in ['serverChan', 'serverChan3']"
:key="it"
:label="it"
:value="it"
/>
</el-select>
</el-form-item>
<el-form-item label="sendKey" v-if="props.notificationConfig.serverChanType === 'serverChan'">
<el-input v-model="props.notificationConfig.serverChanSendKey" placeholder="1234567890"/>
</el-form-item>
<el-form-item label="apiUrl" v-if="props.notificationConfig.serverChanType === 'serverChan3'">
<el-input v-model="props.notificationConfig.serverChan3ApiUrl"
placeholder="https://<uid>.push.ft07.com/send/<sendKey>.send"/>
</el-form-item>
<el-form-item label="事件标题">
<el-switch v-model="props.notificationConfig['serverChanTitleAction']"/>
</el-form-item>
</template>
</template>
<script setup>
let props = defineProps(['notificationConfig', 'config'])
</script>

View File

@@ -0,0 +1,89 @@
<template>
<template v-if="notificationConfig['notificationType'] === 'TELEGRAM'">
<el-form-item label="Api Host">
<el-input v-model:model-value="props.notificationConfig['telegramApiHost']"
placeholder="https://api.telegram.org"/>
</el-form-item>
<el-form-item label="Token">
<el-input v-model:model-value="props.notificationConfig['telegramBotToken']"
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"/>
</el-form-item>
<el-form-item label="ChatId">
<div>
<div style="justify-content: space-between;width: 100%;" class="auto">
<div style="margin-top: 4px;margin-right: 4px;">
<el-input v-model:model-value="props.notificationConfig['telegramChatId']"
placeholder="123456789"/>
</div>
<div class="flex" style="margin-top: 4px;align-items: center;">
<div>
<el-select v-model:model-value="chatId" @change="chatIdChange" style="width: 160px">
<el-option v-for="item in Object.keys(chatIdMap)"
:key="item"
:label="item"
:value="item"/>
</el-select>
</div>
<div style="margin-left: 4px;">
<el-button icon="Refresh" bg text @click="getUpdates" :loading="getUpdatesLoading"/>
</div>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="话题ID">
<el-input-number v-model="props.notificationConfig['telegramTopicId']"
:min="-1" style="width: 160px;"/>
</el-form-item>
<el-form-item label="图片">
<el-switch v-model:model-value="props.notificationConfig['telegramImage']"/>
</el-form-item>
<el-form-item label="格式">
<div style="width: 160px;">
<el-select v-model:model-value="props.notificationConfig['telegramFormat']" placeholder="None">
<el-option label="None" value=""/>
<el-option label="Markdown" value="Markdown"/>
<el-option label="HTML" value="HTML"/>
</el-select>
</div>
</el-form-item>
</template>
</template>
<script setup>
import {ElMessage} from "element-plus";
import api from "../../js/api.js";
import {ref} from "vue";
let chatIdMap = ref({})
let chatId = ref('')
let getUpdatesLoading = ref(false)
let getUpdates = () => {
if (!props.notificationConfig.telegramBotToken.length) {
ElMessage.error('Token 不能为空')
return
}
getUpdatesLoading.value = true
api.post("api/telegram?method=getUpdates", props.notificationConfig)
.then(res => {
chatIdMap.value = res.data
if (Object.keys(chatIdMap.value).length) {
chatId.value = Object.keys(chatIdMap.value)[0]
chatIdChange(chatId.value)
}
})
.finally(() => {
getUpdatesLoading.value = false
})
}
let chatIdChange = (k) => {
props.notificationConfig.telegramChatId = chatIdMap.value[k]
}
let props = defineProps(['notificationConfig', 'config'])
</script>

View File

@@ -0,0 +1,31 @@
<template>
<template label-width="auto" v-if="notificationConfig['notificationType'] === 'WEB_HOOK'">
<el-form-item label="Method">
<el-select v-model:model-value="props.notificationConfig['webHookMethod']">
<el-option v-for="item in ['POST','GET','PUT','DELETE']"
:key="item"
:label="item"
:value="item"/>
</el-select>
</el-form-item>
<el-form-item label="URL">
<el-input v-model:model-value="props.notificationConfig['webHookUrl']" type="textarea"
autosize
placeholder="http://www.xxx.com?text=test_${message}"></el-input>
</el-form-item>
<el-form-item label="Body">
<el-input v-model:model-value="props.notificationConfig['webHookBody']" type="textarea"
:autosize="{ minRows: 2}"
placeholder='{"text":"test_${message}"}'></el-input>
</el-form-item>
<div style="display: flex;justify-content: end;">
<a target="_blank" href="https://docs.wushuo.top/config/message">通知模版示例</a>
</div>
</template>
</template>
<script setup>
let props = defineProps(['notificationConfig', 'config'])
</script>

View File

@@ -26,8 +26,8 @@ import {Ban, Save} from "@vicons/fa";
import {ElMessage} from "element-plus";
let texts = ref([
'不忍直视 1 (请谨慎评价)', '很差 2', '差 3', '较差 4', '不过不失 5',
'还行 6','推荐 7','力荐 8','神作 9','超神作 10 (请谨慎评价)'
'不忍直视 1 (请谨慎评价)', '很差 2', '差 3', '较差 4', '不过不失 5',
'还行 6', '推荐 7', '力荐 8', '神作 9', '超神作 10 (请谨慎评价)'
])
let dialogVisible = ref(false)

View File

@@ -35,7 +35,7 @@
</el-icon>
<span>全局排除</span>
</template>
<Exclude ref="exclude" v-model:exclude="config.exclude" :show-text="true"/>
<Exclude v-model:exclude="config.exclude" :show-text="true"/>
</el-tab-pane>
<el-tab-pane label="代理设置" :lazy="true">
<template #label>
@@ -64,7 +64,7 @@
</template>
<div style="height: 500px;">
<el-scrollbar style="padding: 0 12px">
<Message ref="messageRef" v-model:config="config" v-model:message-active-name="messageActiveName"/>
<Notification ref="notificationRef" v-model:config="config"/>
</el-scrollbar>
</div>
<div style="height: 4px;"></div>
@@ -103,7 +103,7 @@ import {ElMessage} from "element-plus";
import CryptoJS from "crypto-js";
import api from "../js/api.js";
import Exclude from "../config/Exclude.vue";
import Message from "../config/Message.vue";
import Notification from "../config/Notification.vue";
import Proxy from "../config/Proxy.vue";
import Download from "../config/Download.vue";
import Basic from "../config/Basic.vue";
@@ -159,32 +159,11 @@ const config = ref({
'proxyPort': 8080,
'renameSleep': 1,
'downloadCount': 0,
'mail': false,
'mailAddressee': '',
'mailAccount': {
'downloadToolHost': '',
'port': 25,
'from': '',
'pass': '',
'sslEnable': false
},
'mailImage': true,
'login': {
'downloadToolUsername': '',
'downloadToolPassword': ''
},
'telegram': false,
'telegramBotToken': '',
'telegramChatId': '',
'telegramTopicId': -1,
'telegramApiHost': 'https://api.telegram.org',
'webHookUrl': '',
'webHookMethod': '',
'webHookBody': '',
'webHook': false,
'qbRenameTitle': true,
'qbUseDownloadPath': false,
'seasonName': 'Season 1',
'showPlaylist': false,
'enabledExclude': false,
'importExclude': false,
@@ -194,17 +173,9 @@ const config = ref({
'scoreShow': false,
'standbyRss': false,
'downloadNew': false,
'telegramImage': true,
'telegramFormat': '',
'innerIP': false,
'renameTemplate': '',
'messageList': [],
'verifyLoginIp': true,
'serverChanSendKey': '',
'serverChan3ApiUrl': '',
'serverChan': false,
'serverChanType': '',
'systemMsg': false,
'loginEffectiveHours': 3,
'trackersUpdateUrls': '',
'autoTrackersUpdate': false,
@@ -212,7 +183,6 @@ const config = ref({
'tmdbId': false,
'renameDelYear': false,
'renameDelTmdbId': false,
'messageTemplate': '',
'ratioLimit': -2,
'seedingTimeLimit': -2,
'inactiveSeedingTimeLimit': -2,
@@ -222,10 +192,7 @@ const config = ref({
const activeName = ref('download')
const exclude = ref()
const messageActiveName = ref('')
const messageRef = ref()
const notificationRef = ref()
const show = (update) => {
activeName.value = update ? 'about' : 'download'
@@ -261,7 +228,6 @@ defineExpose({
show
})
const emit = defineEmits(['load'])
</script>
<style scoped>

View File

@@ -125,10 +125,9 @@
<script setup>
import {onMounted, ref} from "vue";
import {Back, Delete, Edit as EditIcon, Files} from "@element-plus/icons-vue"
import {Delete, Edit as EditIcon, Files} from "@element-plus/icons-vue"
import Edit from "./Edit.vue";
import api from "../js/api.js";
import Popconfirm from "../other/Popconfirm.vue";
import PlayList from "../play/PlayList.vue";
import Cover from "./Cover.vue";
import Del from "./Del.vue";

View File

@@ -68,7 +68,8 @@
<div class="flex" style="align-items: center;">
<img :src="img(it)" height="40" width="40" @click.stop="open(it.url)">
<div class="flex" style="align-items: center;">
<el-text :truncated="false" line-clamp="1" size="small" style="margin-left: 4px;line-height: 1.6;">
<el-text :truncated="false" line-clamp="1" size="small"
style="margin-left: 4px;line-height: 1.6;">
{{ it.title }}
</el-text>
</div>

View File

@@ -9,7 +9,8 @@
target="_blank">
{{ group.name }}
</el-link>
<el-badge v-if="props.ani.tmdb['tmdbGroupId'] === group.id" class="item" style="margin-left: 4px;" type="primary" value="已选择"/>
<el-badge v-if="props.ani.tmdb['tmdbGroupId'] === group.id" class="item" style="margin-left: 4px;"
type="primary" value="已选择"/>
</div>
<el-button icon="Select" text @click="select(group)"/>
</div>

View File

@@ -0,0 +1,30 @@
export let notificationTypeList = [
{
name: 'TELEGRAM',
label: 'TG通知'
},
{
name: 'MAIL',
label: '邮箱通知'
},
{
name: 'SERVER_CHAN',
label: 'Server酱'
},
{
name: 'SYSTEM',
label: '系统通知'
},
{
name: 'WEB_HOOK',
label: 'WebHook'
},
{
name: 'EMBY_REFRESH',
label: 'Emby媒体库刷新'
}
]
export let getLabel = (name) => {
return notificationTypeList.filter(item => item.name === name)[0].label;
}