Merge branch 'main' of https://gitea.corbz.dev/corbz/PYRSS-Website
This commit is contained in:
commit
8e5498c191
@ -147,4 +147,4 @@ class UserServerLinkSerializer(DynamicModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = UserServerLink
|
||||
fields = ("id", "server_id", "user", "name", "permissions")
|
||||
fields = ("id", "server_id", "user", "name", "icon", "icon_url", "permissions")
|
||||
|
@ -51,7 +51,7 @@ class Subscription_ListView(generics.ListCreateAPIView):
|
||||
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
|
||||
filterset_fields = ["uuid", "name", "rss_url", "server", "targets", "creation_datetime", "extra_notes", "active"]
|
||||
search_fields = ["name", "extra_notes"]
|
||||
ordering_fields = ["creation_datetime"]
|
||||
ordering_fields = ["creation_datetime", "server"]
|
||||
|
||||
def post(self, request):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
|
@ -14,4 +14,4 @@ class DiscordUserAdmin(admin.ModelAdmin):
|
||||
@admin.register(UserServerLink)
|
||||
class UserServerLink(admin.ModelAdmin):
|
||||
|
||||
list_display = ["id", "user", "name", "permissions"]
|
||||
list_display = ["id", "user", "name", "permissions", "icon"]
|
||||
|
@ -1,7 +1,8 @@
|
||||
# Generated by Django 5.0.1 on 2024-03-11 17:38
|
||||
# Generated by Django 5.0.1 on 2024-03-27 11:13
|
||||
|
||||
import apps.authentication.managers
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@ -30,8 +31,16 @@ class Migration(migrations.Migration):
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates whether the user has unrestricted site control.', verbose_name='superuser status')),
|
||||
],
|
||||
managers=[
|
||||
('objects', apps.authentication.managers.DiscordUserOAuth2Manager()),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserServerLink',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('server_id', models.PositiveBigIntegerField()),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('permissions', models.IntegerField()),
|
||||
('icon', models.CharField(help_text="the guild's icon hash", max_length=64, verbose_name='icon')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-03-13 13:45
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelManagers(
|
||||
name='discorduser',
|
||||
managers=[
|
||||
],
|
||||
),
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.1 on 2024-03-27 11:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userserverlink',
|
||||
name='icon',
|
||||
field=models.CharField(blank=True, help_text="the guild's icon hash", max_length=64, null=True, verbose_name='icon'),
|
||||
),
|
||||
]
|
@ -1,24 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-03-17 22:51
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0002_alter_discorduser_managers'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserServerLink',
|
||||
fields=[
|
||||
('id', models.PositiveBigIntegerField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('perm_flags', models.IntegerField()),
|
||||
('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-03-17 23:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0003_userserverlink'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userserverlink',
|
||||
name='perm_flags',
|
||||
field=models.IntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-03-20 10:10
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0004_alter_userserverlink_perm_flags'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='userserverlink',
|
||||
old_name='perm_flags',
|
||||
new_name='permissions',
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-03-20 10:37
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0005_rename_perm_flags_userserverlink_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='userserverlink',
|
||||
old_name='user_id',
|
||||
new_name='user',
|
||||
),
|
||||
]
|
@ -1,30 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-03-20 11:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0006_rename_user_id_userserverlink_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userserverlink',
|
||||
name='server_id',
|
||||
field=models.PositiveBigIntegerField(default=1),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userserverlink',
|
||||
name='id',
|
||||
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userserverlink',
|
||||
name='permissions',
|
||||
field=models.IntegerField(default=1),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -135,10 +135,25 @@ class UserServerLink(models.Model):
|
||||
user = models.ForeignKey(to=DiscordUser, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=64)
|
||||
permissions = models.IntegerField()
|
||||
icon = models.CharField(
|
||||
_("icon"),
|
||||
max_length=64,
|
||||
help_text=_("the guild's icon hash"),
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
|
||||
if not self.icon: # TODO: review this
|
||||
return "/static/images/placeholder-100x100.png"
|
||||
|
||||
return f"https://cdn.discordapp.com/icons/{self.server_id}/{self.icon}.webp?size=80"
|
||||
|
||||
# @property
|
||||
# def is_admin(self):
|
||||
# return self.perm_flags & 0x0000000000000008
|
||||
|
@ -29,6 +29,9 @@ class DiscordLoginRedirect(View):
|
||||
This method will "login" the user.
|
||||
"""
|
||||
|
||||
if request.GET.get("error"):
|
||||
return redirect("auth:login")
|
||||
|
||||
code = request.GET.get("code")
|
||||
access_token = self.exchange_code_for_token(code)
|
||||
raw_user_data = self.get_raw_user_data(access_token)
|
||||
@ -54,7 +57,7 @@ class DiscordLoginRedirect(View):
|
||||
|
||||
# Fetch the access token
|
||||
response = requests.post(
|
||||
url="https://discord.com/api/oauth2/token",
|
||||
url=f"{settings.DISCORD_API_URL}/oauth2/token",
|
||||
data=request_data["data"],
|
||||
headers=request_data["headers"]
|
||||
)
|
||||
@ -70,7 +73,7 @@ class DiscordLoginRedirect(View):
|
||||
"""
|
||||
|
||||
response = requests.get(
|
||||
url="https://discord.com/api/v6/users/@me",
|
||||
url=f"{settings.DISCORD_API_URL}/users/@me",
|
||||
headers={"Authorization": f"Bearer {access_token}"}
|
||||
)
|
||||
|
||||
@ -89,7 +92,7 @@ class GuildsView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
response = requests.get(
|
||||
url="https://discord.com/api/v6/users/@me/guilds",
|
||||
url=f"{settings.DISCORD_API_URL}/users/@me/guilds",
|
||||
headers={"Authorization": f"Bearer {request.user.access_token}"}
|
||||
)
|
||||
|
||||
@ -114,7 +117,8 @@ class GuildsView(View):
|
||||
server_id=server["id"],
|
||||
user=user,
|
||||
name=server["name"],
|
||||
permissions=server["permissions"]
|
||||
permissions=server["permissions"],
|
||||
icon=server["icon"]
|
||||
)
|
||||
for server in content
|
||||
]
|
||||
@ -132,7 +136,7 @@ class GuildChannelsView(View):
|
||||
log.debug("fetching channels from %s using token: %s", guild_id, settings.BOT_TOKEN)
|
||||
|
||||
response = requests.get(
|
||||
url=f"https://discord.com/api/v10/guilds/{guild_id}/channels",
|
||||
url=f"{settings.DISCORD_API_URL}/guilds/{guild_id}/channels",
|
||||
headers={"Authorization": f"Bot {settings.BOT_TOKEN}"}
|
||||
)
|
||||
|
||||
|
@ -242,4 +242,12 @@ body {
|
||||
|
||||
.toast .progress-bar {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Progress Bar */
|
||||
|
||||
@keyframes decreaseProgressWidth {
|
||||
from { width: 100%; }
|
||||
to { width: 0%; }
|
||||
}
|
||||
|
@ -35,6 +35,24 @@ function getSubscription(uuid) {
|
||||
});
|
||||
}
|
||||
|
||||
function getServer(serverId) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
$.ajax({
|
||||
url: `/api/serverlink/?server_id=${serverId}`,
|
||||
type: "GET",
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader("X-CSRFToken", CSRF_MiddlewareToken);
|
||||
},
|
||||
success: function(response) {
|
||||
resolve(response.results[0]);
|
||||
},
|
||||
error: function(response) {
|
||||
reject(response.results[0]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function newSubscription(formData) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
$.ajax({
|
||||
|
@ -1,5 +1,5 @@
|
||||
const toastTemplate = `
|
||||
<div class="toast mt-3" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast mt-3" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="false">
|
||||
<div class="toast-header">
|
||||
<i class="toast-icon bi me-2"></i>
|
||||
<strong class="toast-title me-auto">Bootstrap</strong>
|
||||
@ -12,8 +12,6 @@ const toastTemplate = `
|
||||
</div>
|
||||
`
|
||||
|
||||
const toastFadeDelayMs = 5000;
|
||||
|
||||
const toastTypes = {
|
||||
"primary": "bi-bell-fill",
|
||||
"info": "bi-info-circle-fill",
|
||||
@ -22,24 +20,28 @@ const toastTypes = {
|
||||
"success": "bi-check-circle-fill"
|
||||
}
|
||||
|
||||
function showToast(typeName, title, message) {
|
||||
function animateToastProgress(progressBar, durationMs) {
|
||||
progressBar.css({
|
||||
"animation-duration": (durationMs / 1000) + "s",
|
||||
"animation-name": "decreaseProgressWidth",
|
||||
"animation-fill-mode": "forwards",
|
||||
"animation-timing-function": "linear"
|
||||
});
|
||||
}
|
||||
|
||||
function showToast(typeName, title, message, duration=5000) {
|
||||
var toast = makeToast(typeName, title, message)
|
||||
$(".toasts-container").prepend(toast);
|
||||
$(".toasts-container").append(toast);
|
||||
|
||||
toast.toast("show", {autohide: false});
|
||||
toast.toast("show");
|
||||
|
||||
// var progressPercent = 100;
|
||||
// var progressBar = toast.find(".progress-bar");
|
||||
var progressBar = toast.find(".progress-bar");
|
||||
progressBar.on("animationend", function() {
|
||||
toast.toast("hide");
|
||||
setTimeout(function() {toast.remove()}, 1500);
|
||||
});
|
||||
|
||||
// const toastProgressInterval = setInterval(function() {
|
||||
// progressPercent -= 1;
|
||||
// progressBar.css("width", progressPercent + "%");
|
||||
// if (progressPercent < 0) {
|
||||
// // toast.toast("hide");
|
||||
// // setTimeout(function() {toast.remove(); }, 1500);
|
||||
// clearInterval(toastProgressInterval);
|
||||
// }
|
||||
// }, (toastFadeDelayMs + 1500) / 100);
|
||||
animateToastProgress(progressBar, duration);
|
||||
}
|
||||
|
||||
function makeToast(typeName, title, message) {
|
||||
@ -47,7 +49,7 @@ function makeToast(typeName, title, message) {
|
||||
var template = $(toastTemplate);
|
||||
template.find(".toast-icon").addClass(`text-${typeName}`).addClass(iconClass);
|
||||
template.find(".progress-bar").addClass(`bg-${typeName}`);
|
||||
template.find(".toast-body").text(message);
|
||||
template.find(".toast-body").html(message);
|
||||
template.find(".toast-title").text(title);
|
||||
|
||||
return template;
|
||||
|
@ -83,6 +83,7 @@
|
||||
<label for="editSubChannels">Channels</label>
|
||||
<select name="editSubChannels" id="editSubChannels" class="border-3 bd select-2" data-dropdownparent="#subEditModal" required multiple>
|
||||
</select>
|
||||
<div class="invalid-feedback">Please Select one or more Channels.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="navExtrasPanel" class="tab-pane fade" role="tabpanel" aria-labelledby="navExtrasTab" tabindex="0">
|
||||
@ -143,6 +144,28 @@
|
||||
|
||||
<!-- Specific Page JS goes HERE -->
|
||||
{% block javascripts %}
|
||||
<script id="subCategoryTemplate" type="text/template">
|
||||
<div class="col-12">
|
||||
<hr>
|
||||
<div class="peers mB-20">
|
||||
<div class="peer">
|
||||
<img class="cat-icon rounded-3" width="50" height="50">
|
||||
</div>
|
||||
<div class="peer px-2">
|
||||
<div class="layers align-items-start">
|
||||
<h5 class="layer cat-name mb-1"></h4>
|
||||
<h6 class="layer cat-id"></h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row sub-container">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="subItemTemplate" type="text/template">
|
||||
<div class="col-md-6 col-lg-4 col-xxl-3">
|
||||
<div class="sub-item layers bd bg-body h-100 rounded-3" data-uuid="">
|
||||
@ -170,7 +193,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<button type="button" class="btn bg-body-tertiary waves-effect bd rounded-3 border-0" onclick="showToast('warning', 'Warning', 'not implemented');">
|
||||
<button type="button" class="btn bg-body-tertiary waves-effect bd rounded-3 border-0" onclick="showToast('warning', 'Warning', 'Activity History not implemented yet');">
|
||||
<i class="bi bi-clock-history"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -182,6 +205,11 @@
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="peer ms-3">
|
||||
<button type="button" class="sub-copy btn bg-body-tertiary waves-effect bd rounded-3 border-0">
|
||||
<i class="bi bi-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="peer ms-3">
|
||||
<button type="button" class="sub-delete btn bg-body-tertiary waves-effect bd rounded-3 border-0">
|
||||
<i class="bi bi-trash3"></i>
|
||||
@ -194,4 +222,325 @@
|
||||
</script>
|
||||
<script src="{% static 'js/api.js' %}"></script>
|
||||
<script src="{% static 'js/subscriptions.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
function createSubCategory(serverId) {
|
||||
var template = $($("#subCategoryTemplate").html());
|
||||
|
||||
template.find(".cat-id").text(serverId);
|
||||
|
||||
var server = getServer(serverId).then(resp => {
|
||||
template.find(".cat-icon").attr("src", resp.icon_url);
|
||||
template.find(".cat-name").text(resp.name);
|
||||
});
|
||||
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
function createSubscriptionItem(data) {
|
||||
var template = $($("#subItemTemplate").html());
|
||||
|
||||
// Store the uuid for later reference
|
||||
template.find(".sub-item").attr("data-uuid", data.uuid);
|
||||
|
||||
// Display data
|
||||
template.find(".sub-name").text(data.name);
|
||||
template.find(".sub-uuid").text(data.uuid);
|
||||
template.find(".sub-rss").text(data.rss_url).attr("href", data.rss_url);
|
||||
template.find(".sub-img").attr("src", data.image);
|
||||
template.find(".sub-channel-count").text(data.targets.split(";").length);
|
||||
|
||||
// Display Sub Description
|
||||
if (!data.extra_notes) {
|
||||
template.find(".sub-desc").hide();
|
||||
} else {
|
||||
template.find(".sub-desc").text(data.extra_notes);
|
||||
}
|
||||
|
||||
// Display formatted datetime
|
||||
var displayDate = new Date(data.creation_datetime).toISOString().slice(0, 10);
|
||||
template.find(".sub-datetime").text(displayDate);
|
||||
|
||||
// Provide button functionality
|
||||
template.find(".sub-edit").attr("onclick", `subEditModal("${data.uuid}");`);
|
||||
template.find(".sub-delete").attr("onclick", `confirmUnsubscribe("${data.uuid}");`);
|
||||
|
||||
// Enable tooltips
|
||||
template.find('[data-bs-toggle="tooltip"]').tooltip();
|
||||
|
||||
// Make the switch toggle the active flag
|
||||
template.find(".sub-active").prop("checked", data.active).change(function() {
|
||||
|
||||
var checkbox = $(this);
|
||||
|
||||
checkbox.prop("disabled", true);
|
||||
var isChecked = checkbox.prop("checked");
|
||||
var activeText = isChecked === true ? "active" : "inactive";
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append("active", isChecked);
|
||||
|
||||
patchSubscription(data.uuid, formData).then(function(resp) {
|
||||
showToast("success", "Subscription Modified", `<b>${data.name}</b> is now <b>${activeText}</b>.`);
|
||||
checkbox.prop("disabled", false);
|
||||
})
|
||||
});
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
loadGuilds();
|
||||
loadSubscriptions();
|
||||
});
|
||||
|
||||
function loadGuilds() {
|
||||
$.ajax({
|
||||
url: "/guilds",
|
||||
type: "GET",
|
||||
success: function(response) {
|
||||
|
||||
// Add each guild as a selectable option
|
||||
for (i = 1; i < response.length; i++) {
|
||||
var guild = response[i];
|
||||
var option = $("<option>", {text: guild.name, value: guild.id})
|
||||
$("#editSubServer").append(option);
|
||||
}
|
||||
|
||||
// Bind the select to update channels on change
|
||||
$("#editSubServer").change(function() {
|
||||
var selectedGuildId = $(this).find("option:selected").attr("value");
|
||||
loadChannels(selectedGuildId);
|
||||
});
|
||||
},
|
||||
error: function(response) {
|
||||
console.error(JSON.stringify(response, null, 4));
|
||||
showToast("danger", `Error Loading Guilds`, "Couldn't load user guilds. Try refreshing the page.", 15000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processChannelsResponseError(response) {
|
||||
switch (response.code) {
|
||||
|
||||
// 50001:
|
||||
// Forbidden response
|
||||
case 50001:
|
||||
showToast(
|
||||
"danger",
|
||||
`Discord API Error: ${response.code}`,
|
||||
`PYRSS Bot is lacking forbidden from fetching channels for this server.
|
||||
Ensure that at least one condition is true:
|
||||
<ul>
|
||||
<li>The server is a community server.</li>
|
||||
<li>PYRSS Bot is a member of the server.</li>
|
||||
</ul>
|
||||
`,
|
||||
10000
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
showToast("danger", `Discord API Error: ${response.code}`, response.message, 10000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function loadChannels(guildID) {
|
||||
$("#editSubChannels").empty();
|
||||
$("#editSubChannels").val(null);
|
||||
$.ajax({
|
||||
url: `/channels?guild=${guildID}`,
|
||||
type: "GET",
|
||||
success: function(response) {
|
||||
|
||||
// Validate the response, if property "code" exists, there is an error.
|
||||
// A valid response only returns a list.
|
||||
if (response.hasOwnProperty("code")) {
|
||||
processChannelsResponseError(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.hasOwnProperty("code") && response.code === 50001) {
|
||||
showToast(
|
||||
"danger", "Unable to fetch channels",
|
||||
"PYRSS Bot is lacking permissions to fetch the channels from this server, ensure one of two conditions is met: <br><ul><li>The server is a community server</li><li>PYRSS Bot is a member of the server</li></ul>",
|
||||
10000
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 1; i < response.length; i++) {
|
||||
var channel = response[i];
|
||||
|
||||
if (channel.type !== 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
var selectedChannelIDs;
|
||||
|
||||
try {
|
||||
selectedChannelIDs = $("#editSubChannels").attr("data-current").split(";");
|
||||
}
|
||||
catch {
|
||||
selectedChannelIDs = [];
|
||||
}
|
||||
|
||||
$("#editSubChannels").append($("<option>", {
|
||||
value: channel.id,
|
||||
text: "#" + channel.name,
|
||||
selected: selectedChannelIDs.includes(channel.id.toString())
|
||||
}));
|
||||
}
|
||||
},
|
||||
error: function(response) {
|
||||
alert(JSON.stringify(response, null, 4));
|
||||
// showToast("danger", `Error Loading Guilds`, "Couldn't load user guilds. Try refreshing the page.", 15000);
|
||||
if (response.code == "50001") {
|
||||
alert("PYRSS Bot is Missing Access to this Server");
|
||||
}
|
||||
else {
|
||||
alert("unknown error fetching channels " + response.code)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateSubscriptionCount(difference, overwrite) {
|
||||
const beforeChange = overwrite ? 0 : Number($(".subs-count").text());
|
||||
$(".subs-count").text(beforeChange + difference);
|
||||
}
|
||||
|
||||
function loadSubscriptions() {
|
||||
$("#subscriptionContainer").empty();
|
||||
getSubscriptions(ordering="server").then(resp => {
|
||||
updateSubscriptionCount(resp.results.length, true);
|
||||
|
||||
var categorisedSubs = {};
|
||||
|
||||
$.each(resp.results, function(index, sub) {
|
||||
categorisedSubs[sub.server] = categorisedSubs[sub.server] || [];
|
||||
categorisedSubs[sub.server].push(sub);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(categorisedSubs, null, 4))
|
||||
|
||||
$.each(categorisedSubs, function(server, subs) {
|
||||
var categoryElem = createSubCategory(server);
|
||||
$("#subscriptionContainer").append(categoryElem);
|
||||
|
||||
$.each(subs, function(index, sub) {
|
||||
var subElem = createSubscriptionItem(sub);
|
||||
categoryElem.find(".sub-container").append(subElem);
|
||||
});
|
||||
})
|
||||
|
||||
// for (i = 0; i < resp.results.length; i++) {
|
||||
// var sub = resp.results[i];
|
||||
// console.log(JSON.stringify(sub));
|
||||
// var subElem = createSubscriptionItem(sub);
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
function confirmUnsubscribe(uuid) {
|
||||
var title = $(`.sub-item[data-uuid='${uuid}'] .sub-name`).text();
|
||||
$(".del-sub-name").text(title);
|
||||
$(".del-sub-uuid").text(uuid);
|
||||
$(".del-sub-confirm").attr("onclick", `unsubscribe("${uuid}")`)
|
||||
$("#subDeleteModal").modal("show");
|
||||
}
|
||||
|
||||
function unsubscribe(uuid) {
|
||||
var subElem = $(`#subscriptionContainer .sub-item[data-uuid="${uuid}"]`);
|
||||
subElem.find("button").prop("disabled", true);
|
||||
var subName = subElem.find(".sub-name").text();
|
||||
|
||||
deleteSubscription(uuid).then(resp => {
|
||||
subElem.parent().remove();
|
||||
updateSubscriptionCount(-1);
|
||||
$("#subDeleteModal").modal("hide");
|
||||
showToast(
|
||||
"success",
|
||||
"Deleted Subscription",
|
||||
`Successfully deleted <b>${subName}</b>.`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function subEditModal(uuid) {
|
||||
|
||||
var modal = $("#subEditModal");
|
||||
|
||||
modal.find("input").val(null);
|
||||
modal.find("textarea").val(null);
|
||||
modal.find("select").val("");
|
||||
$("#editSubChannels").empty();
|
||||
$("#subEditForm").removeClass("was-validated");
|
||||
$("#navDetailsTab").click();
|
||||
|
||||
if (uuid === -1) {
|
||||
modal.find(".modal-title").text("New Subscription");
|
||||
}
|
||||
else {
|
||||
modal.find(".modal-title").text("Edit Subscription");
|
||||
|
||||
getSubscription(uuid).then(resp => {
|
||||
// alert(JSON.stringify(resp, null, 4));
|
||||
$("#editSubName").val(resp.name);
|
||||
$("#editSubURL").val(resp.rss_url);
|
||||
$("#editSubServer").val(String(resp.server)).trigger("change");
|
||||
$("#editSubChannels").attr("data-current", resp.targets);
|
||||
$("#editSubNotes").val(resp.extra_notes);
|
||||
});
|
||||
}
|
||||
|
||||
$("#subEditModal").attr("data-uuid", uuid);
|
||||
$("#subEditModal").modal("show");
|
||||
}
|
||||
|
||||
function submitSubEditModal() {
|
||||
// Validation
|
||||
var form = $("#subEditForm");
|
||||
if (!form[0].checkValidity()) {
|
||||
form.addClass("was-validated");
|
||||
return;
|
||||
}
|
||||
|
||||
const uuid = $("#subEditModal").attr("data-uuid");
|
||||
const subName = $("#editSubName").val();
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append("uuid", uuid);
|
||||
formData.append("name", subName);
|
||||
formData.append("rss_url", $("#editSubURL").val());
|
||||
formData.append("server", $("#editSubServer").val());
|
||||
formData.append("extra_notes", $("#editSubNotes").val());
|
||||
formData.append("active", true);
|
||||
|
||||
var selectedTargets = $("#editSubChannels option:selected").toArray().map(item => item.value).join(';');
|
||||
formData.append("targets", selectedTargets);
|
||||
|
||||
var imageFile = $("#editSubImage")[0].files[0];
|
||||
if (imageFile) {
|
||||
formData.append("image", imageFile);
|
||||
}
|
||||
|
||||
if (uuid === "-1") {
|
||||
newSubscription(formData).then(resp => {
|
||||
loadSubscriptions();
|
||||
$("#subEditModal").modal("hide");
|
||||
showToast("success", "Subscription Created", `<b>${subName}</b> successfully created.`);
|
||||
})
|
||||
}
|
||||
else {
|
||||
editSubscription(uuid, formData).then(resp => {
|
||||
loadSubscriptions();
|
||||
$("#subEditModal").modal("hide");
|
||||
showToast("success", "Subscription Modified", `<b>${subName}</b> successfully modified.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock javascripts %}
|
||||
|
@ -27,7 +27,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="sidebar-menu">
|
||||
<li class="nav-item">
|
||||
<a class="sidebar-link" href="/">
|
||||
<span class="icon-holder">
|
||||
<i class="c-blue-500 ti-home"></i>
|
||||
</span>
|
||||
<span class="title">Subscriptions</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- ### $Sidebar Menu ### -->
|
||||
<!-- <li class="nav-item">
|
||||
<hr class="bgc-grey-500">
|
||||
|
@ -140,6 +140,7 @@ DISCORD_CODE_EXCHANGE_REQUEST = {
|
||||
"scope": " ".join(DISCORD_SCOPES)
|
||||
}
|
||||
}
|
||||
DISCORD_API_URL = env("DISCORD_API_URL")
|
||||
DISCORD_OAUTH2_URL = env("DISCORD_OAUTH2_URL")
|
||||
|
||||
# Logging
|
||||
|
Loading…
x
Reference in New Issue
Block a user