添加BARK支持

This commit is contained in:
xpnas 2021-04-03 23:26:09 +08:00
parent d4b1f9faf2
commit 939f64549c
24 changed files with 535 additions and 309 deletions

View File

@ -24,6 +24,7 @@
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"qrcodejs": "^1.0.0",
"vue": "2.6.10",
"vue-router": "3.0.6",
"vuex": "3.1.0"

View File

@ -12,7 +12,7 @@
<el-input v-model="selectTemplate.name"></el-input>
</el-form-item>
<el-form-item v-for="(item) in selectTemplate.values" :label="item.description" :key="item.name" required>
<el-input v-model="item.value" :placeholder="item.default"></el-input>
<el-input v-model="item.value" :placeholder="item.default" :readonly="item.readonly"></el-input>
</el-form-item>
</el-form>
@ -71,6 +71,7 @@ import {
getSendTemplates,
addAuthInfo,
deepClone,
getSendKey
} from '@/api/setting'
@ -102,7 +103,7 @@ export default {
isModify: false,
authform: {},
title: "设置",
sendKey: ""
}
},
created() {
@ -117,14 +118,23 @@ export default {
})
getSendTemplates().then((response) => {
if (response.code == 200) {
this.sendTemplates = response.data;
}
})
getSendKey().then(response => {
if (response.code == 200) {
this.sendKey = response.data;
}
})
},
selectTemplateChange(selectTemplate) {
this.selectTemplate = deepClone(selectTemplate);
this.selectTemplate = deepClone(selectTemplate)
if (this.selectTemplate.warning) {
this.$message({
message: this.selectTemplate.warning,
type: 'warning'
})
}
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
@ -181,8 +191,23 @@ export default {
this.title = '修改设置'
this.isModify = true;
this.selectTemplate = deepClone(row);
this.dialogVisible = true;
if (this.selectTemplate.type == "Bark") {
let wPath = window.document.location.href;
let pathName = this.$route.path;
let pos = wPath.indexOf(pathName);
let localhostPath = wPath.substring(0, pos);
localhostPath = localhostPath.replace("#", "");
var devieItem = this.selectTemplate.values.find(item => {
return item.name == "DeviceKey"
})
var sendUrlItem = this.selectTemplate.values.find(item => {
return item.name == "SendUrl"
});
sendUrlItem.value = localhostPath + "?act=" + this.sendKey + "/" + devieItem.value + "/{title}/{data}"
}
this.dialogVisible = true;
},
deleteAuth(index, row) {
deleteAuthInfo(row.sendAuthId).then((response) => {
@ -213,24 +238,25 @@ export default {
}
}
</script>
<style lang="scss">
<style lang="scss">
@media screen and (min-width:800px) {
.el-dialog__wrapper .el-dialog {
width: 800px !important;
}
.el-dialog__wrapper .el-dialog .el-dialog__body {
overflow: auto
}
}
@media screen and (max-width: 800px) {
@media screen and (max-width: 800px) {
.el-dialog__wrapper .el-dialog {
width: 99% !important;
}
.el-dialog__wrapper .el-dialog .el-dialog__body {
overflow: auto
}
}
</style>

View File

@ -26,13 +26,23 @@
<el-form-item label="快捷地址(标题)">
<el-input type="textarea" v-model="keyForm.sendUrlTitle" :readonly="true"></el-input>
</el-form-item>
<el-form-item label="快捷地址(完整)">
<el-form-item label="快捷地址(完整)">
<el-input type="textarea" v-model="keyForm.sendUrl" :readonly="true"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onReSendKey('resetform')">重置SendKey</el-button>
</el-form-item>
</el-form>
<el-divider content-position="left">BARK授权</el-divider>
<el-form ref="resetform" :model="keyForm" label-width="20%">
<el-form-item label="绑定地址">
<el-input v-model="keyForm.barkUrl" :readonly="true"></el-input>
</el-form-item>
<el-form-item>
<div id="qrcode"> </div>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
@ -95,10 +105,15 @@ export default {
let pos = wPath.indexOf(pathName);
let localhostPath = wPath.substring(0, pos);
localhostPath = localhostPath.replace("#", "");
this.keyForm.sendUrlTitle = localhostPath + 'api/'+ this.keyForm.sendKey+'.send' + "/{title}"
this.keyForm.sendUrl = localhostPath + 'api/'+ this.keyForm.sendKey+'.send' + "/{title}/{data}"
// this.keyForm.sendUrl = localhostPath + 'api/send?token=' + this.keyForm.sendKey + "&title={title}&data={data}"
this.keyForm.sendUrlTitle = localhostPath + this.keyForm.sendKey + '.send' + "/{title}"
this.keyForm.sendUrl = localhostPath + this.keyForm.sendKey + '.send' + "/{title}/{data}"
this.keyForm.barkUrl = wPath.substring(0, pos - 2) + '?act=' + this.keyForm.sendKey
this.listLoading = false;
new QRCode(document.getElementById("qrcode"), {
text: this.keyForm.barkUrl,
width: 150,
height: 150,
});
});
},
onMessage(fromname) {

View File

@ -36,6 +36,17 @@
<el-switch active-color='#13ce66' v-model='settingForm.githubEnable' inactive-color='#ff4949'></el-switch>
</template>
</el-form-item>
<el-divider content-position="left">BARK授权</el-divider>
<el-form-item label="KeyID">
<el-input v-model="settingForm.barkKeyId" placeholder="KeyID">></el-input>
</el-form-item>
<el-form-item label="TempID">
<el-input v-model="settingForm.barkTeamId" placeholder="TempID"></el-input>
</el-form-item>
<el-form-item label="P8Key">
<el-input type="textarea" v-model="settingForm.barkPrivateKey" placeholder="P8Key"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('settingForm')">确认修改</el-button>
</el-form-item>

View File

@ -7,7 +7,7 @@ function resolve(dir) {
}
const name = defaultSettings.title || 'Inotify'
const port = process.env.port || process.env.npm_config_port || 9528
const port = process.env.port || process.env.npm_config_port || 9000
const axiosV = require('axios/package.json').version
const echartsV = require('echarts/package.json').version
@ -43,7 +43,8 @@ const cdn = {
`https://cdn.bootcdn.net/ajax/libs/vue-router/${routerV}/vue-router.min.js`,
`https://cdn.bootcdn.net/ajax/libs/element-ui/${elementV}/locale/zh-CN.js`,
`https://cdn.bootcdn.net/ajax/libs/js-cookie/${cookieV}/js.cookie.min.js`,
`https://cdn.bootcdn.net/ajax/libs/nprogress/${nprogressV}/nprogress.min.js`
`https://cdn.bootcdn.net/ajax/libs/nprogress/${nprogressV}/nprogress.min.js`,
`https://cdn.bootcdn.net/ajax/libs/qrcodejs/1.0.0/qrcode.js`
]
}

View File

@ -17,3 +17,9 @@ dotnet_diagnostic.CS8602.severity = none
# CS8600: 将 null 文本或可能的 null 值转换为不可为 null 类型。
dotnet_diagnostic.CS8600.severity = none
# IDE0037: 使用推断的成员名称
dotnet_diagnostic.IDE0037.severity = none
# IDE0008: 使用显式类型
dotnet_diagnostic.IDE0008.severity = none

View File

@ -9,6 +9,8 @@ namespace Inotify.Common
{
public static class Extensions
{
static int rep = 0;
/// <summary>
/// MD5加密字符串32位大写
/// </summary>
@ -30,6 +32,7 @@ namespace Inotify.Common
public static string Base64Encode(this string source)
{
if (string.IsNullOrEmpty(source)) return "";
byte[] bytes = (Encoding.UTF8.GetBytes(source));
return Convert.ToBase64String(bytes);
@ -37,8 +40,32 @@ namespace Inotify.Common
public static string Base64Decode(this string source)
{
if (string.IsNullOrEmpty(source)) return "";
var bytes = Convert.FromBase64String(source);
return System.Text.Encoding.Default.GetString(bytes);
}
public static string GenerateCheckCode(this int codeCount)
{
string str = string.Empty;
long num2 = DateTime.Now.Ticks + rep;
rep++;
Random random = new Random(((int)(((ulong)num2) & 0xffffffffL)) | ((int)(num2 >> rep)));
for (int i = 0; i < codeCount; i++)
{
char ch;
int num = random.Next();
if ((num % 2) == 0)
{
ch = (char)(0x30 + ((ushort)(num % 10)));
}
else
{
ch = (char)(0x41 + ((ushort)(num % 0x1a)));
}
str += ch.ToString();
}
return str;
}
}
}

View File

@ -1,4 +1,5 @@
using Inotify.Data;
using Inotify.Common;
using Inotify.Data;
using Inotify.Data.Models;
using Inotify.Sends;
using Inotify.Sends.Products;
@ -13,106 +14,115 @@ using System.Threading.Tasks;
namespace Inotify.Controllers
{
public class DeviceInfo
{
public string? Token { get; set; }
public string? Key { get; set; }
public string? DeviceToken { get; set; }
public string? Device_key { get; set; }
public string? Device_token { get; set; }
}
[ApiController]
[Route("/")]
public class BarkControlor : BaseControlor
{
[HttpGet, Route("Ping")]
public JsonResult Ping(string? token)
public JsonResult Ping()
{
return OK();
return Me("pong");
}
[HttpGet, Route("Info")]
public JsonResult Info(string? token)
public JsonResult Info()
{
var dateTime = System.IO.File.GetLastWriteTime(this.GetType().Assembly.Location);
var devices = DBManager.Instance.DBase.Query<SendAuthInfo>().Count();
return Json(new
{
version = "v2",
build = "2021.03.29",
version = "v2.0.1",
build = dateTime.ToString("yyyy-MM-dd HH:mm:ss"),
arch = RuntimeInformation.OSDescription,
commit = "inotfy",
devices = RuntimeInformation.OSDescription
commit = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(),
devices
});
}
[HttpGet, Route("Healthz")]
public string Healthz(string? token)
public string Healthz()
{
return "ok";
}
[HttpGet, Route("Register")]
public JsonResult Register(string? act, string? key, string? devicetoken, string? device_key) => !string.IsNullOrEmpty(device_key) ?
Register(device_key) : Register(act, key, devicetoken);
[HttpPost, Route("Register")]
public JsonResult Register(DeviceInfo deviceInfo)
public JsonResult Register(string? act, string? device_key, string? device_token)
{
if (!string.IsNullOrEmpty(deviceInfo.Key))
deviceInfo.Device_key = deviceInfo.Key;
if (string.IsNullOrEmpty(act))
return Fail(400, "request bind failed : act is empty");
if (!string.IsNullOrEmpty(deviceInfo.DeviceToken))
deviceInfo.Device_token = deviceInfo.DeviceToken;
if (string.IsNullOrEmpty(device_token))
return Fail(400, "request bind failed : device_token is empty");
if (string.IsNullOrEmpty(deviceInfo.Device_key))
return Fail(400, "request bind failed : device_key is empty");
if (string.IsNullOrEmpty(deviceInfo.Device_token))
return Fail(400, "request bind failed : device_token not empty");
var userInfo = DBManager.Instance.DBase.Query<SendUserInfo>().FirstOrDefault(e => e.Token == deviceInfo.Token);
var userInfo = DBManager.Instance.DBase.Query<SendUserInfo>().FirstOrDefault(e => e.Token == act);
if (userInfo == null)
{
return Fail(400, "request bind failed : device not registered");
return Fail(400, "request bind failed : act is not registered");
}
else
{
var barkAuth = new BarkAuth() { DeviceToken = deviceInfo.Device_token };
BarkAuth barkAuth = null;
SendAuthInfo barkSendAuthInfo = null;
var barkTemplateAttribute = typeof(BarkSendTemplate).GetCustomAttributes(typeof(SendMethodKeyAttribute), false).OfType<SendMethodKeyAttribute>().First();
var barkSendAuthInfo = DBManager.Instance.DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.UserId == userInfo.Id && e.SendMethodTemplate == barkTemplateAttribute.Key);
if (!string.IsNullOrEmpty(device_key))
{
barkSendAuthInfo = DBManager.Instance.DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.Key == device_key);
if (barkSendAuthInfo != null)
{
barkAuth = JsonConvert.DeserializeObject<BarkAuth>(barkSendAuthInfo.AuthData);
barkAuth.DeviceToken = device_token;
barkSendAuthInfo.AuthData = JsonConvert.SerializeObject(barkAuth);
barkSendAuthInfo.ModifyTime = DateTime.Now;
DBManager.Instance.DBase.Update(barkSendAuthInfo);
}
}
if (barkSendAuthInfo == null)
{
device_key = 16.GenerateCheckCode();
barkAuth = new BarkAuth() { DeviceKey = device_key, DeviceToken = device_token, IsArchive = "1", AutoMaticallyCopy = "1", Sound = "1107" };
barkSendAuthInfo = new SendAuthInfo()
{
Name = barkTemplateAttribute.Name,
SendMethodTemplate = barkTemplateAttribute.Key,
Key = device_key,
AuthData = JsonConvert.SerializeObject(barkAuth),
UserId = userInfo.Id,
CreateTime = DateTime.Now,
ModifyTime = DateTime.Now
ModifyTime = DateTime.Now,
Active = true,
};
var sendAuthId = Convert.ToInt32(DBManager.Instance.DBase.Insert<SendAuthInfo>(barkSendAuthInfo));
userInfo.SendAuthId = sendAuthId;
DBManager.Instance.DBase.Update(userInfo, e => e.SendAuthId);
}
else
{
barkSendAuthInfo.AuthData = JsonConvert.SerializeObject(barkAuth);
barkSendAuthInfo.ModifyTime = DateTime.Now;
DBManager.Instance.DBase.Update(barkSendAuthInfo);
DBManager.Instance.DBase.Insert(barkSendAuthInfo);
}
return Json(new
{
key = deviceInfo.Device_key,
device_key = deviceInfo.Device_key,
device_token = deviceInfo.Device_token
key = device_key,
device_key = device_key,
device_token = device_token
});
}
}
[HttpGet, Route("RegisterCheck")]
public JsonResult Register(string device_key)
{
if (string.IsNullOrEmpty(device_key))
{
return Fail(400, "device key is empty");
}
if (!DBManager.Instance.DBase.Query<SendAuthInfo>().Any(e => e.Key == device_key))
{
return Fail(400, "device not registered");
}
return OK();
}
}
}

View File

@ -70,7 +70,7 @@ namespace Inotify.Controllers
{
code = 200,
message = "sucess",
timestamp = (DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds
timestamp = (int)(DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds
});
}
@ -81,7 +81,17 @@ namespace Inotify.Controllers
code = 200,
message = "sucess",
data = obj ?? "",
timestamp = (DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds
timestamp = (int)(DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds
});
}
protected JsonResult Me(string message)
{
return Json(new
{
code = 200,
message = message,
timestamp = (int)(DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds
});
}
@ -91,7 +101,7 @@ namespace Inotify.Controllers
{
code = 404,
message = "failed",
timestamp = (DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds
timestamp = (int)(DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds
});
}
@ -99,9 +109,9 @@ namespace Inotify.Controllers
{
return Json(new
{
code,
code= code,
message = "failed",
timestamp = (DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds
timestamp = (int)(DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds
});
}
@ -109,9 +119,9 @@ namespace Inotify.Controllers
{
return new JsonResult(new
{
code,
message,
timestamp = (DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds
code=code,
message=message,
timestamp = (int)(DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds
});
}

View File

@ -12,20 +12,39 @@ namespace Inotify.Controllers
public class SendController : BaseControlor
{
[HttpGet, Route("send")]
public JsonResult Send(string token, string title, string? data)
public JsonResult Send(string token, string title, string? data, string? key)
{
if (DBManager.Instance.IsSendKey(token))
if (DBManager.Instance.IsToken(token, out bool hasActive))
{
if (!hasActive) return Fail(400, "you have no tunnel is acitve");
if (!string.IsNullOrEmpty(key))
{
if (DBManager.Instance.IsSendKey(key, out bool isActive))
{
if (!isActive)
{
return Fail(400, $"device:{key} tunnel is not acitve");
}
}
else
{
return Fail(400, $"device:{key} is not registered");
}
}
var message = new SendMessage()
{
Token = token,
Title = title,
Data = data,
Key = key,
};
if (SendTaskManager.Instance.SendMessage(message))
return OK();
if (SendTaskManager.Instance.SendMessage(message)) return OK();
}
return Fail();
return Fail(400, $"token:{token} is not registered");
}
}
}

View File

@ -39,6 +39,7 @@ namespace Inotify.Controllers
var userInfo = DBManager.Instance.GetUser(UserName);
if (userInfo != null)
{
var barkTemplateAttribute = typeof(BarkSendTemplate).GetCustomAttributes(typeof(SendMethodKeyAttribute), false).OfType<SendMethodKeyAttribute>().First();
var sendAuthInfos = DBManager.Instance.DBase.Query<SendAuthInfo>().Where(e => e.UserId == userInfo.Id).ToArray();
var userSendTemplates = new List<InputTemeplate>();
foreach (var sendAuthInfo in sendAuthInfos)
@ -53,6 +54,10 @@ namespace Inotify.Controllers
sendTemplate.AuthToTemplate(sendAuthInfo.AuthData);
userSendTemplates.Add(sendTemplate);
}
if (barkTemplateAttribute.Key == sendTemplate.Key)
{
sendTemplate.Values.FirstOrDefault(e => e.Name == nameof(BarkSendTemplate.Auth.SendUrl)).Value = "";
}
}
return OK(userSendTemplates);
@ -100,10 +105,9 @@ namespace Inotify.Controllers
if (userInfo != null && inputTemeplate.Key != null && inputTemeplate.Name != null)
{
var barkKey = typeof(BarkSendTemplate).GetCustomAttributes(typeof(SendMethodKeyAttribute), false).OfType<SendMethodKeyAttribute>().First().Key;
if (barkKey == inputTemeplate.Key
&& DBManager.Instance.DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.UserId == userInfo.Id && e.SendMethodTemplate == barkKey) != null)
if (barkKey == inputTemeplate.Key)
{
return Fail(406, "您只能添加一个BARK通道");
return Fail(406, "BARK通道勿手动添加请使用APP添加BARK地址绑定");
}
else
{
@ -131,6 +135,7 @@ namespace Inotify.Controllers
var userInfo = DBManager.Instance.GetUser(UserName);
if (userInfo != null)
{
var barkTemplateAttribute = typeof(BarkSendTemplate).GetCustomAttributes(typeof(SendMethodKeyAttribute), false).OfType<SendMethodKeyAttribute>().First();
var oldSendInfo = DBManager.Instance.DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.Id == inputTemeplate.SendAuthId);
if (oldSendInfo != null && inputTemeplate.Name != null)
{

View File

@ -30,6 +30,9 @@ namespace Inotify.Controllers
githubClientID = SendCacheStore.GetSystemValue("githubClientID"),
githubClientSecret = SendCacheStore.GetSystemValue("githubClientSecret"),
githubEnable = githubEnable != "" && bool.Parse(githubEnable),
barkKeyId= SendCacheStore.GetSystemValue("barkKeyId"),
barkTeamId = SendCacheStore.GetSystemValue("barkTeamId"),
barkPrivateKey = SendCacheStore.GetSystemValue("barkPrivateKey"),
});
}
@ -41,7 +44,10 @@ namespace Inotify.Controllers
string? proxyenable,
string? githubClientID,
string? githubClientSecret,
string? githubEnable)
string? githubEnable,
string? barkKeyId,
string? barkTeamId,
string? barkPrivateKey)
{
SendCacheStore.SetSystemValue("sendthread", sendthread);
SendCacheStore.SetSystemValue("administrators", administrators);
@ -50,6 +56,9 @@ namespace Inotify.Controllers
SendCacheStore.SetSystemValue("githubClientID", githubClientID);
SendCacheStore.SetSystemValue("githubClientSecret", githubClientSecret);
SendCacheStore.SetSystemValue("githubEnable", githubEnable);
SendCacheStore.SetSystemValue("barkKeyId", barkKeyId);
SendCacheStore.SetSystemValue("barkTeamId", barkTeamId);
SendCacheStore.SetSystemValue("barkPrivateKey", barkPrivateKey);
SendTaskManager.Instance.Stop();
SendTaskManager.Instance.Run();

View File

@ -105,16 +105,38 @@ namespace Inotify.Data
m_dbConnection = new SqliteConnection(string.Format("Data Source={0}", m_dbPath));
if (m_dbConnection.State == ConnectionState.Closed)
m_dbConnection.Open();
DBase = new NPoco.Database(m_dbConnection, DatabaseType.SQLite);
DBase = new Database(m_dbConnection, DatabaseType.SQLite);
DBase.KeepConnectionAlive = true;
m_migrator = new Migrator(DBase);
}
public bool IsSendKey(string token)
public bool IsToken(string token,out bool hasActive)
{
return DBase.Query<SendUserInfo>().Any(e => e.Token == token);
hasActive = false;
var userInfo= DBase.Query<SendUserInfo>().FirstOrDefault(e => e.Token == token);
if (userInfo != null)
{
hasActive= DBase.Query<SendAuthInfo>().Any(e => e.UserId== userInfo.Id && e.Active);
return true;
}
return false;
}
public bool IsSendKey(string key, out bool isActive)
{
isActive = false;
var sendAuthInfo = DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.Key == key);
if (sendAuthInfo != null)
{
isActive = sendAuthInfo.Active;
return true;
}
return false;
}
public bool IsUser(string userName)
@ -124,10 +146,10 @@ namespace Inotify.Data
public SendUserInfo GetUser(string userName)
{
return DBase.Query<SendUserInfo>().First(e => e.UserName == userName);
return DBase.Query<SendUserInfo>().FirstOrDefault(e => e.UserName == userName);
}
public string GetAuth(string token, out string guid)
public string GetSendAuthInfo(string token, out string guid)
{
guid = string.Empty;
var upToekn = token.ToUpper();
@ -142,18 +164,25 @@ namespace Inotify.Data
return authInfo.AuthData;
}
public void GetAuth(string token, out SendAuthInfo[] sendAuthInfos)
public void GetSendAuthInfos(string token, string key, out SendAuthInfo[] sendAuthInfos)
{
sendAuthInfos = null;
var upToekn = token.ToUpper();
var userInfo = DBManager.Instance.DBase.Query<SendUserInfo>().FirstOrDefault(e => e.Token == upToekn && e.Active);
if (userInfo != null)
sendAuthInfos = DBManager.Instance.DBase.Query<SendAuthInfo>().Where(e => e.UserId == userInfo.Id && e.Active).ToArray();
{
if (string.IsNullOrEmpty(key))
{
sendAuthInfos = DBManager.Instance.DBase.Query<SendAuthInfo>().Where(e => e.UserId == userInfo.Id && e.Active).ToArray();
}
else
{
sendAuthInfos = DBManager.Instance.DBase.Query<SendAuthInfo>().Where(e => e.UserId == userInfo.Id && e.Active &&e.Key==key).ToArray();
}
}
}
public void Run()
{
@ -175,10 +204,9 @@ namespace Inotify.Data
var builder = new MigrationBuilder(MigrationName, DBase);
builder.Append(new Version(2, 0, 0, 0), new V2UpdateMigration());
builder.Append(new Version(2, 0, 0, 1), new V2001UpdateMigration());
builder.Execute();
}
}
}
}

View File

@ -59,8 +59,7 @@ namespace Inotify.Data
{
protected override void execute()
{
//V2版本允许多通道,激活标记放入SendAuthInfo表中增加Active列
//更新原有用户的激活通道
//V2版本允许多通道,激活标记放入SendAuthInfo表中增加Active列同时更新原有用户的激活通道
Migrator.AlterTable<SendAuthInfo>().AddColumn(e => e.Active).Execute();
Migrator.Database.UpdateMany<SendAuthInfo>().OnlyFields(e => e.Active).Execute(new SendAuthInfo() { Active = false });
var activeUsers = Migrator.Database.Query<SendUserInfo>().ToList();
@ -75,4 +74,38 @@ namespace Inotify.Data
});
}
}
public class V2001UpdateMigration : Migration, IMigration
{
protected override void execute()
{
//V2001版本增加SendInfo的key字段
Migrator.AlterTable<SendAuthInfo>().AddColumn(e => e.Key).Execute();
//对AuthInfo的AuthDate字段进行加密
var sendAuthInfos = Migrator.Database.Query<SendAuthInfo>().ToList();
sendAuthInfos.ForEach(sendAuthInfo =>
{
sendAuthInfo.AuthData = sendAuthInfo.AuthDataSave;
Migrator.Database.Update(sendAuthInfo);
});
//添加bark密钥相关内容
Migrator.Database.Insert(new SystemInfo()
{
key = "barkKeyId",
Value = "TEg0VDlWNVU0Ug==".Base64Decode(),
});
Migrator.Database.Insert(new SystemInfo()
{
key = "barkTeamId",
Value = "NVU4TEJSWEczQQ==".Base64Decode(),
});
Migrator.Database.Insert(new SystemInfo()
{
key = "barkPrivateKey",
Value = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR1RBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJIa3dkd0lCQVFRZzR2dEMzZzVMNUhnS0dKMitUMWVBMHRPaXZSRXZFQVkyZytqdVJYSmtZTDJnQ2dZSUtvWkl6ajBEQVFlaFJBTkNBQVNtT3MzSmtTeW9HRVdac1VHeEZzLzRwdzFySWxTVjJJQzE5TTh1M0c1a3EzNnVwT3d5RldqOUdpM0VqYzlkM3NDNytTSFJxWHJFQUpvdzgvN3RScFYrCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=".Base64Decode()
});
}
}
}

View File

@ -1,5 +1,5 @@
using System;
using Inotify.Common;
namespace Inotify.Data.Models
{
[NPoco.TableName("sendAuthInfo")]
@ -19,7 +19,20 @@ namespace Inotify.Data.Models
public string SendMethodTemplate { get; set; }
[NPoco.Column("authData")]
public string AuthData { get; set; }
public string AuthDataSave { get; set; }
[NPoco.Ignore]
public string AuthData
{
get
{
return AuthDataSave.Base64Decode();
}
set
{
AuthDataSave = value.Base64Encode();
}
}
[NPoco.Column("modifyTime")]
public DateTime ModifyTime { get; set; }
@ -29,5 +42,9 @@ namespace Inotify.Data.Models
[NPoco.Column("active")]
public bool Active { get; set; }
[NPoco.Column("key")]
public string Key { get; set; }
}
}

View File

@ -6,8 +6,8 @@
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<StartupObject>Inotify.Program</StartupObject>
<Nullable>enable</Nullable>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
<FileVersion>2.0.0.0</FileVersion>
<AssemblyVersion>2.0.0.1</AssemblyVersion>
<FileVersion>2.0.0.1</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -31,7 +31,7 @@
<ItemGroup>
<None Update="inotify_data\AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

View File

@ -1,9 +1,11 @@
using Inotify.Common;
using Inotify.Data;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Net.Http;
@ -16,166 +18,78 @@ using System.Threading.Tasks;
namespace Inotify.Sends.Products
{
public class BarkMessage
{
public BarkMessage(string body) : this(string.Empty, body) { }
public BarkMessage(string title, string body)
{
this.Title = title;
this.Body = body;
}
#region
/// <summary>
/// 标题,加粗
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// 正文
/// </summary>
public string Body { get; set; } = string.Empty;
/// <summary>
/// 自动保存
/// </summary>
public string IsArchive { get; set; } = "1";
/// <summary>
/// 链接
/// </summary>
public string Url { get; set; } = string.Empty;
/// <summary>
/// 自动复制
/// </summary>
public string AutoMaticallyCopy { get; set; } = "0";
/// <summary>
/// 复制文本
/// </summary>
public string Copy { get; set; } = string.Empty;
#endregion
#region
/// <summary>
/// 设置链接
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public BarkMessage SetUrl(string url)
{
this.Url = url;
return this;
}
/// <summary>
/// 设置保存,默认保存
/// </summary>
/// <returns></returns>
public BarkMessage SetArchive()
{
IsArchive = "1";
return this;
}
/// <summary>
/// 设置不保存,默认保存
/// </summary>
/// <returns></returns>
public BarkMessage SetNotArchive()
{
IsArchive = "0";
return this;
}
/// <summary>
/// 设置自动复制,默认不自动复制
/// </summary>
/// <returns></returns>
public BarkMessage SetAutoCopy()
{
this.AutoMaticallyCopy = "1";
return this;
}
/// <summary>
/// 设置不自动复制,默认不自动复制
/// </summary>
/// <returns></returns>
public BarkMessage SetNotAutoCopy()
{
this.AutoMaticallyCopy = "1";
return this;
}
/// <summary>
/// 设置自动拷贝的文本,默认拷贝全文
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public BarkMessage SetCopyText(string text)
{
Copy = text;
return this;
}
#endregion
}
public class BarkAuth
{
[InputTypeAttribte(1, "Sound", "声音", "1107")]
public string Sound { get; set; }
[InputTypeAttribte(0, "IsArchive", "自动保存", "1")]
[InputTypeAttribte(2, "IsArchive", "自动保存", "1或0")]
public string IsArchive { get; set; }
[InputTypeAttribte(0, "AutoMaticallyCopy", "自动复制", "0")]
[InputTypeAttribte(3, "AutoMaticallyCopy", "自动复制", "1或0")]
public string AutoMaticallyCopy { get; set; }
[InputTypeAttribte(0, "DeviceToken", "DeviceToken", "DeviceToken",false)]
[InputTypeAttribte(4, "DeviceKey", "DeviceKey", "DeviceKey", true, true)]
public string DeviceKey { get; set; }
[InputTypeAttribte(5, "DeviceToken", "DeviceToken", "DeviceToken", true, true)]
public string DeviceToken { get; set; }
[InputTypeAttribte(6, "SendUrl", "SendUrl", "SendUrl", true, true)]
public string SendUrl { get; set; }
}
[SendMethodKey("3B6DE04D-A9EF-4C91-A151-60B7425C5AB2", "Bark(未完成)", Order = -1)]
[SendMethodKey("3B6DE04D-A9EF-4C91-A151-60B7425C5AB2", "Bark", Order = 6, Waring = "BARK通道勿手动添加请使用APP添加BARK地址绑定")]
public class BarkSendTemplate : SendTemplate<BarkAuth>
{
private static readonly string Topic = "me.fin.bark";
private static readonly string KeyID = "LH4T9V5U4R";
private static readonly string TeamID = "5U8LBRXG3A";
private static string KeyID;
private static string TeamID;
private static CngKey SecretKey;
public override BarkAuth Auth { get; set; }
public override bool SendMessage(SendMessage message)
{
var barkMessage = new BarkMessage(message.Title, message.Data)
{
IsArchive = Auth.IsArchive,
AutoMaticallyCopy = Auth.AutoMaticallyCopy
};
SendMesssage(barkMessage, Auth.DeviceToken);
return false;
}
private bool SendMesssage(BarkMessage barkMessage, string device_Tokne)
{
if (SecretKey == null)
{
var authPath = Path.Combine(DBManager.Instance.Inotify_Data, "AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8");
var privateKeyContent = File.ReadAllText(authPath).Split('\n')[1];
SecretKey = CngKey.Import(Convert.FromBase64String(privateKeyContent), CngKeyBlobFormat.Pkcs8PrivateBlob);
KeyID = SendCacheStore.GetSystemValue("barkKeyId");
TeamID = SendCacheStore.GetSystemValue("barkTeamId");
var privateKey = SendCacheStore.GetSystemValue("barkPrivateKey");
var privateKeyContent = privateKey.Split('\n')[1];
var decodeKey = Convert.FromBase64String(privateKeyContent);
SecretKey = CngKey.Import(decodeKey, CngKeyBlobFormat.Pkcs8PrivateBlob);
}
if (barkMessage == null)
if (Auth.DeviceToken == null)
return false;
if (device_Tokne == null)
return false;
var payload = CreatePayload(message);
var accessToken = CreateAccessToken(payload);
var result = CreatePush(payload, accessToken, Auth.DeviceToken);
return result;
}
private string CreatePayload(SendMessage message)
{
var expiration = DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var expirationSeconds = (long)expiration.TotalSeconds;
var alert = new Dictionary<string, object>();
if (!string.IsNullOrEmpty(barkMessage.Body))
alert.Add("body", barkMessage.Body);
if (!string.IsNullOrEmpty(barkMessage.Title))
alert.Add("title", barkMessage.Title);
if (!string.IsNullOrEmpty(message.Data))
alert.Add("body", message.Data);
if (!string.IsNullOrEmpty(message.Title))
alert.Add("title", message.Title);
var aps = new Dictionary<string, object>
{
{ "category", "Bark" },
{ "sound", "1107" },
{ "sound", Auth.Sound },
{ "badge", "0" },
{ "mutable-content", "1" },
{ "alert", alert }
@ -184,65 +98,88 @@ namespace Inotify.Sends.Products
var payload = new Dictionary<string, object>
{
{ "aps", aps },
{ "isarchive", barkMessage.IsArchive },
{ "automaticallycopy", barkMessage.AutoMaticallyCopy },
{ "isarchive", Auth.IsArchive },
{ "automaticallycopy", Auth.AutoMaticallyCopy },
{ "iss", TeamID},
{ "iat", expirationSeconds}
};
if (!string.IsNullOrEmpty(barkMessage.Url))
payload.Add("url", barkMessage.Url);
if (!string.IsNullOrEmpty(message.Title))
payload.Add("copy", message.Title);
if (!string.IsNullOrEmpty(barkMessage.Copy))
payload.Add("copy", barkMessage.Copy);
var headers = new
{
alg = "ES256",
kid = KeyID
};
var hearderString = JObject.FromObject(headers).ToString();
var payloadString = JObject.FromObject(payload).ToString();
var accessToken = SignES256(SecretKey, hearderString, payloadString);
var data = Encoding.UTF8.GetBytes(payloadString);
var httpClient = new HttpClient();
var requestMessage = new HttpRequestMessage
{
RequestUri = new Uri(string.Format("https://{0}:{1}/3/device/{2}", "api.development.push.apple.com", 443, device_Tokne))
};
requestMessage.Headers.Add("authorization", string.Format("bearer {0}", accessToken));
requestMessage.Headers.Add("apns-id", Guid.NewGuid().ToString());
requestMessage.Headers.Add("apns-expiration", "0");
requestMessage.Headers.Add("apns-priority", "10");
requestMessage.Headers.Add("apns-topic", Topic);
requestMessage.Method = HttpMethod.Post;
requestMessage.Content = new ByteArrayContent(data);
var task = httpClient.SendAsync(requestMessage);
task.Wait();
var responseMessage = task.Result;
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
return true;
}
return true;
return payloadString;
}
private string SignES256(CngKey secretKey, string header, string payload)
private string CreateAccessToken(string payload)
{
using ECDsaCng dsa = new ECDsaCng(secretKey)
using ECDsaCng dsa = new ECDsaCng(SecretKey)
{
HashAlgorithm = CngAlgorithm.Sha256
};
var unsignedJwtData = Convert.ToBase64String(Encoding.UTF8.GetBytes(header)) + "." + Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var headers = JObject.FromObject(new
{
alg = "ES256",
kid = KeyID
}).ToString();
var unsignedJwtData = Convert.ToBase64String(Encoding.UTF8.GetBytes(headers)) + "." + Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var signature = dsa.SignData(Encoding.UTF8.GetBytes(unsignedJwtData));
return unsignedJwtData + "." + Convert.ToBase64String(signature);
}
private bool CreatePush(string payload, string accessToken, string device_Tokne)
{
try
{
var data = Encoding.UTF8.GetBytes(payload);
var request = new HttpRequestMessage
{
Version = new Version(2, 0),
RequestUri = new Uri($"https://api.development.push.apple.com:443/3/device/{device_Tokne}")
};
request.Headers.Add("authorization", string.Format("bearer {0}", accessToken));
request.Headers.Add("apns-id", Guid.NewGuid().ToString());
request.Headers.Add("apns-expiration", "0");
request.Headers.Add("apns-priority", "10");
request.Headers.Add("apns-topic", "me.fin.bark");
request.Method = HttpMethod.Post;
request.Content = new ByteArrayContent(data);
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Connection.Add("Keep-Alive");
var task = httpClient.SendAsync(request);
task.Wait();
var responseMessage = task.Result;
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
var _response_uuid = "";
if (responseMessage.Headers.TryGetValues("apns-id", out IEnumerable<string> values))
{
_response_uuid = values.First();
Console.WriteLine($"success: '{_response_uuid}'");
return true;
}
else
return false;
}
else
{
var respoinseBody = responseMessage.Content.ReadAsStringAsync().Result;
var responseJson = JObject.Parse(respoinseBody);
var reason = responseJson.Value<string>("reason");
Console.WriteLine($"failure: '{reason}'");
}
}
catch (Exception ex)
{
Console.WriteLine($"exception: '{ex.Message}'");
}
return false;
}
}
}

View File

@ -9,5 +9,6 @@ namespace Inotify.Sends
public string Token;
public string Title;
public string? Data;
public string? Key;
}
}

View File

@ -37,7 +37,6 @@ namespace Inotify.Sends
private readonly Dictionary<string, Type> m_sendMethodTemplateTypes;
private SendTaskManager()
{
m_sendMessages = new BlockingCollection<SendMessage>();
@ -70,7 +69,6 @@ namespace Inotify.Sends
m_analyseThread.Start();
}
public EventHandler<SendMessage> OnMessageAdd;
public EventHandler<SendMessage> OnSendSucessed;
@ -152,7 +150,7 @@ namespace Inotify.Sends
try
{
var message = m_sendMessages.Take();
DBManager.Instance.GetAuth(message.Token, out SendAuthInfo[] sendAuthInfos);
DBManager.Instance.GetSendAuthInfos(message.Token, message.Key, out SendAuthInfo[] sendAuthInfos);
foreach (var authInfo in sendAuthInfos)
{
var authData = authInfo.AuthData;
@ -212,7 +210,7 @@ namespace Inotify.Sends
{
var message = m_analyseMessages.Take();
var date = DateTime.Now.ToString("yyyyMMdd");
var authData = DBManager.Instance.GetAuth(message.Token, out string temeplateId);
var authData = DBManager.Instance.GetSendAuthInfo(message.Token, out string temeplateId);
if (temeplateId != null)
{

View File

@ -31,16 +31,25 @@ namespace Inotify.Sends
public int Order;
public SendMethodKeyAttribute(string key, string name, bool open = true)
public string Waring;
public SendMethodKeyAttribute(string key, string name, bool open = true, string waring = "")
{
Key = key;
Name = name;
Open = open;
Waring = waring;
}
}
public class InputTypeValue
{
public InputTypeValue()
{
Show = true;
Readonly = false;
}
public string? Name { get; set; }
public string? Description { get; set; }
public string? Default { get; set; }
@ -48,7 +57,11 @@ namespace Inotify.Sends
public int? Order { get; set; }
public string? Value { get; set; }
public bool Visuable { get; set; }
public string? Warning { get; set; }
public bool Show { get; set; }
public bool Readonly { get; set; }
}
public class InputTemeplate
@ -57,6 +70,7 @@ namespace Inotify.Sends
public string? Type { get; set; }
public string? Name { get; set; }
public bool IsActive { get; set; }
public string Warning { get; set; }
public string? AuthData { get; set; }
public int? SendAuthId { get; set; }
public List<InputTypeValue>? Values { get; set; }
@ -112,7 +126,7 @@ namespace Inotify.Sends
public InputTypeValue InputTypeData { get; set; }
private InputTypeAttribte(int order, string name, string description, string defaultValue, InputType type,bool visuable=true)
private InputTypeAttribte(int order, string name, string description, string defaultValue, InputType type,bool show=true,bool readOnly=false)
{
InputTypeData = new InputTypeValue
{
@ -121,18 +135,19 @@ namespace Inotify.Sends
Default = defaultValue,
Order = order,
Type = type,
Visuable=visuable
Show = show,
Readonly = readOnly
};
}
public InputTypeAttribte(int order, string name, string description, string defaultValue, bool visuable = true)
: this(order, name, description, defaultValue, InputType.TEXT, visuable)
public InputTypeAttribte(int order, string name, string description, string defaultValue, bool show = true, bool readOnly = false)
: this(order, name, description, defaultValue, InputType.TEXT, show, readOnly)
{
}
public InputTypeAttribte(int order, string name, string description, bool defaultValue, bool visuable = true)
: this(order, name, description, "", InputType.CHECK, visuable)
public InputTypeAttribte(int order, string name, string description, bool defaultValue, bool show = true, bool readOnly = false)
: this(order, name, description, "", InputType.CHECK, show, readOnly)
{
InputTypeData.Default = defaultValue ? "是" : "否";
}
@ -156,7 +171,6 @@ namespace Inotify.Sends
.SelectMany(e => e.GetCustomAttributes(typeof(InputTypeAttribte), false))
.Cast<InputTypeAttribte>()
.Select(e => e.InputTypeData)
.Where(e=>e.Visuable)
.ToList();
var sendMethodKeyAttribute = this.GetType().GetCustomAttribute<SendMethodKeyAttribute>();
@ -168,6 +182,7 @@ namespace Inotify.Sends
Name = sendMethodKeyAttribute.Name,
Type = sendMethodKeyAttribute.Name,
Key = sendMethodKeyAttribute.Key,
Warning = sendMethodKeyAttribute.Waring,
Values = values
};
}

View File

@ -20,6 +20,7 @@ using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using System.Threading.Tasks;
@ -96,26 +97,63 @@ namespace Inotify
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStaticFiles();
app.UseFileServer();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var options = new RewriteOptions();
options.AddRewrite(@"api/(.*).send/(.*)/(.*)", "api/send?token=$1&title=$2&data=$3", true);
options.AddRewrite(@"api/(.*).send/(.*)", "api/send?token=$1&title=$2", true);
options.AddRewrite(@"\?act=(.*)/{.*}/(.*)/(.*)", "api/send?token=$1&title=$3&data=$4", true);
options.AddRewrite(@"\?act=(.*)/{.*}/(.*)", "api/send?token=$1&title=$3", true);
options.Add(rewriteContext =>
{
Match match;
if (rewriteContext.HttpContext.Request.Path == "/")
{
var queryValue = rewriteContext.HttpContext.Request.QueryString.Value;
match = Regex.Match(queryValue, @"^\?act=(.*)/(.*)/(.*)/(.*)$");
var groups = match.Groups;
if (match.Success)
{
rewriteContext.HttpContext.Request.Path = @"/api/send";
rewriteContext.HttpContext.Request.QueryString = new QueryString($"?token={groups[1]}&key={groups[2]}&title={groups[3]}&date={groups[4]}");
}
else
{
match = Regex.Match(queryValue, @"^\?act=(.*)/(.*)/(.*)$");
if (match.Success)
{
rewriteContext.HttpContext.Request.Path = @"/api/send";
rewriteContext.HttpContext.Request.QueryString = new QueryString($"?token={groups[1]}&key={groups[2]}&title={groups[3]}");
}
else
{
match = Regex.Match(queryValue, @"^\?act=(.*)/(.*)$");
if (match.Success)
{
rewriteContext.HttpContext.Request.Path = @"/api/send";
rewriteContext.HttpContext.Request.QueryString = new QueryString($"?token={groups[1]}&key={groups[2]}");
}
else if(rewriteContext.HttpContext.Request.QueryString.Value.StartsWith("?"))
{
rewriteContext.HttpContext.Request.Path = @"/info";
rewriteContext.HttpContext.Request.QueryString = new QueryString();
}
}
}
}
rewriteContext.Result = RuleResult.ContinueRules;
});
//https://im.xpnas.com/?act=123456/ZtCLMPWQWtjJQpKmQS6hoV/
options.AddRewrite(@"^(.*).send/(.*)/(.*)", "api/send?token=$1&title=$2&data=$3", true);
options.AddRewrite(@"^(.*).send/(.*)", "api/send?token=$1&title=$2", true);
options.AddRewrite(@"^api/(.*).send/(.*)/(.*)", "api/send?token=$1&title=$2&data=$3", true);
options.AddRewrite(@"^api/(.*).send/(.*)", "api/send?token=$1&title=$2", true);
app.UseRewriter(options);
app.UseRouting();
app.UseStaticFiles();
app.UseFileServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>

View File

@ -26,7 +26,7 @@ namespace Inotify.ThridOauth.Service
private readonly string _authorizeUrl;
public GitHubLogin(IHttpContextAccessor contextAccessor, IOptions<GitHubCredential> options) : base(
public GitHubLogin(IHttpContextAccessor contextAccessor) : base(
contextAccessor)
{
Credential = new CredentialSetting()

View File

@ -22,30 +22,49 @@
- [x] 企业微信应用消息
- [x] 电报机器人消息
- [x] SMTP邮箱消息
- [x] BARK
- [ ] 钉钉群机器人
- [ ] 飞书群机器人
- [x] 自定义
## 使用方法
1. Docker安装
```
docker run --name=inotify -d -p 8000:80 -v inotify_data:/inotify_data --restart=always xpnas/inotify:latest
```
## 更新日志
* V1.0
* 支持企业微信应用、电报、SMTP消息
* V2.0.0.1
* 支持自定义Get、POST
* V2.0.0.2
* 支持BARK
## 使用方法
1. Docker安装
* 稳定版V1.0
```
docker run --name=inotify -d -p 8000:80 -v inotify_data:/inotify_data --restart=always xpnas/inotify:latest
```
* 开发版V2.0.0.2
```
docker run --name=inotify -d -p 8000:80 -v inotify_data:/inotify_data --restart=always xpnas/inotify:master
```
2. 配置Nginx代理
```
server
{
location / { proxy_pass http://127.0.0.1:8000; }
}
```
```
server
{
location / { proxy_pass http://127.0.0.1:8000; }
}
```
3. 进入`Github/Settings/Developer settings/OAuth Apps`创建应用
* 记录`Client ID`,创建`Client secrets`
* `Authorization callback URL`回调地址填写https://{您的域名}/api/oauth/githubLogin
4. 使用`默认用户名admin密码123456`登陆后台/全局参数,修改Github登陆的`应用ID`、`应用密钥`并启动登陆
5. 建议将`管理权限`的用户名设置成自己的github用户名再使用Github登陆后在用户管理页面`删除默认账号admin`
## BARK设置
1. 本项目依据Bark-Server接口规范实现了内置BARK服务端
2. 复制或扫码`消息验证\BARK授权`中的地址填入BARK应用的服务器地址中,如`https://inotify.cf?act=6D474C0DB1474F19BD8F7342D570C0FC`
3. BARK的APP会自动在本系统注册数据记录将直接出现在`消息通道`
## 系统截图
![](../master/public/A.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 104 KiB