Improve Scraper Control UI, Improve torrent specification selection, Fix date selection type & Add support for exclude files annotation in series import

This commit is contained in:
mhdzumair
2025-01-31 22:06:27 +05:30
parent 71bcadf5a5
commit 946270707b
7 changed files with 1694 additions and 656 deletions
+795
View File
@@ -0,0 +1,795 @@
.flatpickr-calendar {
background: transparent;
opacity: 0;
display: none;
text-align: center;
visibility: hidden;
padding: 0;
-webkit-animation: none;
animation: none;
direction: ltr;
border: 0;
font-size: 14px;
line-height: 24px;
border-radius: 5px;
position: absolute;
width: 307.875px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-ms-touch-action: manipulation;
touch-action: manipulation;
background: #3f4458;
-webkit-box-shadow: 1px 0 0 #20222c, -1px 0 0 #20222c, 0 1px 0 #20222c, 0 -1px 0 #20222c, 0 3px 13px rgba(0,0,0,0.08);
box-shadow: 1px 0 0 #20222c, -1px 0 0 #20222c, 0 1px 0 #20222c, 0 -1px 0 #20222c, 0 3px 13px rgba(0,0,0,0.08);
}
.flatpickr-calendar.open,
.flatpickr-calendar.inline {
opacity: 1;
max-height: 640px;
visibility: visible;
}
.flatpickr-calendar.open {
display: inline-block;
z-index: 99999;
}
.flatpickr-calendar.animate.open {
-webkit-animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.flatpickr-calendar.inline {
display: block;
position: relative;
top: 2px;
}
.flatpickr-calendar.static {
position: absolute;
top: calc(100% + 2px);
}
.flatpickr-calendar.static.open {
z-index: 999;
display: block;
}
.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7) {
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1) {
-webkit-box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
}
.flatpickr-calendar .hasWeeks .dayContainer,
.flatpickr-calendar .hasTime .dayContainer {
border-bottom: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.flatpickr-calendar .hasWeeks .dayContainer {
border-left: 0;
}
.flatpickr-calendar.hasTime .flatpickr-time {
height: 40px;
border-top: 1px solid #20222c;
}
.flatpickr-calendar.noCalendar.hasTime .flatpickr-time {
height: auto;
}
.flatpickr-calendar:before,
.flatpickr-calendar:after {
position: absolute;
display: block;
pointer-events: none;
border: solid transparent;
content: '';
height: 0;
width: 0;
left: 22px;
}
.flatpickr-calendar.rightMost:before,
.flatpickr-calendar.arrowRight:before,
.flatpickr-calendar.rightMost:after,
.flatpickr-calendar.arrowRight:after {
left: auto;
right: 22px;
}
.flatpickr-calendar.arrowCenter:before,
.flatpickr-calendar.arrowCenter:after {
left: 50%;
right: 50%;
}
.flatpickr-calendar:before {
border-width: 5px;
margin: 0 -5px;
}
.flatpickr-calendar:after {
border-width: 4px;
margin: 0 -4px;
}
.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowTop:after {
bottom: 100%;
}
.flatpickr-calendar.arrowTop:before {
border-bottom-color: #20222c;
}
.flatpickr-calendar.arrowTop:after {
border-bottom-color: #3f4458;
}
.flatpickr-calendar.arrowBottom:before,
.flatpickr-calendar.arrowBottom:after {
top: 100%;
}
.flatpickr-calendar.arrowBottom:before {
border-top-color: #20222c;
}
.flatpickr-calendar.arrowBottom:after {
border-top-color: #3f4458;
}
.flatpickr-calendar:focus {
outline: 0;
}
.flatpickr-wrapper {
position: relative;
display: inline-block;
}
.flatpickr-months {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.flatpickr-months .flatpickr-month {
background: #3f4458;
color: #fff;
fill: #fff;
height: 34px;
line-height: 1;
text-align: center;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
overflow: hidden;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-decoration: none;
cursor: pointer;
position: absolute;
top: 0;
height: 34px;
padding: 10px;
z-index: 3;
color: #fff;
fill: #fff;
}
.flatpickr-months .flatpickr-prev-month.flatpickr-disabled,
.flatpickr-months .flatpickr-next-month.flatpickr-disabled {
display: none;
}
.flatpickr-months .flatpickr-prev-month i,
.flatpickr-months .flatpickr-next-month i {
position: relative;
}
.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month.flatpickr-prev-month {
/*
/*rtl:begin:ignore*/
/*
*/
left: 0;
/*
/*rtl:end:ignore*/
/*
*/
}
/*
/*rtl:begin:ignore*/
/*
/*rtl:end:ignore*/
.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,
.flatpickr-months .flatpickr-next-month.flatpickr-next-month {
/*
/*rtl:begin:ignore*/
/*
*/
right: 0;
/*
/*rtl:end:ignore*/
/*
*/
}
/*
/*rtl:begin:ignore*/
/*
/*rtl:end:ignore*/
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
color: #eee;
}
.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
fill: #f64747;
}
.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
width: 14px;
height: 14px;
}
.flatpickr-months .flatpickr-prev-month svg path,
.flatpickr-months .flatpickr-next-month svg path {
-webkit-transition: fill 0.1s;
transition: fill 0.1s;
fill: inherit;
}
.numInputWrapper {
position: relative;
height: auto;
}
.numInputWrapper input,
.numInputWrapper span {
display: inline-block;
}
.numInputWrapper input {
width: 100%;
}
.numInputWrapper input::-ms-clear {
display: none;
}
.numInputWrapper input::-webkit-outer-spin-button,
.numInputWrapper input::-webkit-inner-spin-button {
margin: 0;
-webkit-appearance: none;
}
.numInputWrapper span {
position: absolute;
right: 0;
width: 14px;
padding: 0 4px 0 2px;
height: 50%;
line-height: 50%;
opacity: 0;
cursor: pointer;
border: 1px solid rgba(255,255,255,0.15);
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.numInputWrapper span:hover {
background: rgba(192,187,167,0.1);
}
.numInputWrapper span:active {
background: rgba(192,187,167,0.2);
}
.numInputWrapper span:after {
display: block;
content: "";
position: absolute;
}
.numInputWrapper span.arrowUp {
top: 0;
border-bottom: 0;
}
.numInputWrapper span.arrowUp:after {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid rgba(255,255,255,0.6);
top: 26%;
}
.numInputWrapper span.arrowDown {
top: 50%;
}
.numInputWrapper span.arrowDown:after {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid rgba(255,255,255,0.6);
top: 40%;
}
.numInputWrapper span svg {
width: inherit;
height: auto;
}
.numInputWrapper span svg path {
fill: rgba(255,255,255,0.5);
}
.numInputWrapper:hover {
background: rgba(192,187,167,0.05);
}
.numInputWrapper:hover span {
opacity: 1;
}
.flatpickr-current-month {
font-size: 135%;
line-height: inherit;
font-weight: 300;
color: inherit;
position: absolute;
width: 75%;
left: 12.5%;
padding: 7.48px 0 0 0;
line-height: 1;
height: 34px;
display: inline-block;
text-align: center;
-webkit-transform: translate3d(0px, 0px, 0px);
transform: translate3d(0px, 0px, 0px);
}
.flatpickr-current-month span.cur-month {
font-family: inherit;
font-weight: 700;
color: inherit;
display: inline-block;
margin-left: 0.5ch;
padding: 0;
}
.flatpickr-current-month span.cur-month:hover {
background: rgba(192,187,167,0.05);
}
.flatpickr-current-month .numInputWrapper {
width: 6ch;
width: 7ch\0;
display: inline-block;
}
.flatpickr-current-month .numInputWrapper span.arrowUp:after {
border-bottom-color: #fff;
}
.flatpickr-current-month .numInputWrapper span.arrowDown:after {
border-top-color: #fff;
}
.flatpickr-current-month input.cur-year {
background: transparent;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: inherit;
cursor: text;
padding: 0 0 0 0.5ch;
margin: 0;
display: inline-block;
font-size: inherit;
font-family: inherit;
font-weight: 300;
line-height: inherit;
height: auto;
border: 0;
border-radius: 0;
vertical-align: initial;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
}
.flatpickr-current-month input.cur-year:focus {
outline: 0;
}
.flatpickr-current-month input.cur-year[disabled],
.flatpickr-current-month input.cur-year[disabled]:hover {
font-size: 100%;
color: rgba(255,255,255,0.5);
background: transparent;
pointer-events: none;
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
appearance: menulist;
background: #3f4458;
border: none;
border-radius: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
font-size: inherit;
font-family: inherit;
font-weight: 300;
height: auto;
line-height: inherit;
margin: -1px 0 0 0;
outline: none;
padding: 0 0 0 0.5ch;
position: relative;
vertical-align: initial;
-webkit-box-sizing: border-box;
-webkit-appearance: menulist;
-moz-appearance: menulist;
width: auto;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:focus,
.flatpickr-current-month .flatpickr-monthDropdown-months:active {
outline: none;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
background: rgba(192,187,167,0.05);
}
.flatpickr-current-month .flatpickr-monthDropdown-months .flatpickr-monthDropdown-month {
background-color: #3f4458;
outline: none;
padding: 0;
}
.flatpickr-weekdays {
background: transparent;
text-align: center;
overflow: hidden;
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 28px;
}
.flatpickr-weekdays .flatpickr-weekdaycontainer {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
span.flatpickr-weekday {
cursor: default;
font-size: 90%;
background: #3f4458;
color: #fff;
line-height: 1;
margin: 0;
text-align: center;
display: block;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
font-weight: bolder;
}
.dayContainer,
.flatpickr-weeks {
padding: 1px 0 0 0;
}
.flatpickr-days {
position: relative;
overflow: hidden;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: start;
-webkit-align-items: flex-start;
-ms-flex-align: start;
align-items: flex-start;
width: 307.875px;
}
.flatpickr-days:focus {
outline: 0;
}
.dayContainer {
padding: 0;
outline: 0;
text-align: left;
width: 307.875px;
min-width: 307.875px;
max-width: 307.875px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display: inline-block;
display: -ms-flexbox;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
-ms-flex-wrap: wrap;
-ms-flex-pack: justify;
-webkit-justify-content: space-around;
justify-content: space-around;
-webkit-transform: translate3d(0px, 0px, 0px);
transform: translate3d(0px, 0px, 0px);
opacity: 1;
}
.dayContainer + .dayContainer {
-webkit-box-shadow: -1px 0 0 #20222c;
box-shadow: -1px 0 0 #20222c;
}
.flatpickr-day {
background: none;
border: 1px solid transparent;
border-radius: 150px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: rgba(255,255,255,0.95);
cursor: pointer;
font-weight: 400;
width: 14.2857143%;
-webkit-flex-basis: 14.2857143%;
-ms-flex-preferred-size: 14.2857143%;
flex-basis: 14.2857143%;
max-width: 39px;
height: 39px;
line-height: 39px;
margin: 0;
display: inline-block;
position: relative;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.flatpickr-day.inRange,
.flatpickr-day.prevMonthDay.inRange,
.flatpickr-day.nextMonthDay.inRange,
.flatpickr-day.today.inRange,
.flatpickr-day.prevMonthDay.today.inRange,
.flatpickr-day.nextMonthDay.today.inRange,
.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day:focus,
.flatpickr-day.prevMonthDay:focus,
.flatpickr-day.nextMonthDay:focus {
cursor: pointer;
outline: 0;
background: #646c8c;
border-color: #646c8c;
}
.flatpickr-day.today {
border-color: #eee;
}
.flatpickr-day.today:hover,
.flatpickr-day.today:focus {
border-color: #eee;
background: #eee;
color: #3f4458;
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
background: #80cbc4;
-webkit-box-shadow: none;
box-shadow: none;
color: #fff;
border-color: #80cbc4;
}
.flatpickr-day.selected.startRange,
.flatpickr-day.startRange.startRange,
.flatpickr-day.endRange.startRange {
border-radius: 50px 0 0 50px;
}
.flatpickr-day.selected.endRange,
.flatpickr-day.startRange.endRange,
.flatpickr-day.endRange.endRange {
border-radius: 0 50px 50px 0;
}
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
-webkit-box-shadow: -10px 0 0 #80cbc4;
box-shadow: -10px 0 0 #80cbc4;
}
.flatpickr-day.selected.startRange.endRange,
.flatpickr-day.startRange.startRange.endRange,
.flatpickr-day.endRange.startRange.endRange {
border-radius: 50px;
}
.flatpickr-day.inRange {
border-radius: 0;
-webkit-box-shadow: -5px 0 0 #646c8c, 5px 0 0 #646c8c;
box-shadow: -5px 0 0 #646c8c, 5px 0 0 #646c8c;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay,
.flatpickr-day.notAllowed,
.flatpickr-day.notAllowed.prevMonthDay,
.flatpickr-day.notAllowed.nextMonthDay {
color: rgba(255,255,255,0.3);
background: transparent;
border-color: transparent;
cursor: default;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
cursor: not-allowed;
color: rgba(255,255,255,0.1);
}
.flatpickr-day.week.selected {
border-radius: 0;
-webkit-box-shadow: -5px 0 0 #80cbc4, 5px 0 0 #80cbc4;
box-shadow: -5px 0 0 #80cbc4, 5px 0 0 #80cbc4;
}
.flatpickr-day.hidden {
visibility: hidden;
}
.rangeMode .flatpickr-day {
margin-top: 1px;
}
.flatpickr-weekwrapper {
float: left;
}
.flatpickr-weekwrapper .flatpickr-weeks {
padding: 0 12px;
-webkit-box-shadow: 1px 0 0 #20222c;
box-shadow: 1px 0 0 #20222c;
}
.flatpickr-weekwrapper .flatpickr-weekday {
float: none;
width: 100%;
line-height: 28px;
}
.flatpickr-weekwrapper span.flatpickr-day,
.flatpickr-weekwrapper span.flatpickr-day:hover {
display: block;
width: 100%;
max-width: none;
color: rgba(255,255,255,0.3);
background: transparent;
cursor: default;
border: none;
}
.flatpickr-innerContainer {
display: block;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
.flatpickr-rContainer {
display: inline-block;
padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.flatpickr-time {
text-align: center;
outline: 0;
display: block;
height: 0;
line-height: 40px;
max-height: 40px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.flatpickr-time:after {
content: "";
display: table;
clear: both;
}
.flatpickr-time .numInputWrapper {
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
width: 40%;
height: 40px;
float: left;
}
.flatpickr-time .numInputWrapper span.arrowUp:after {
border-bottom-color: rgba(255,255,255,0.95);
}
.flatpickr-time .numInputWrapper span.arrowDown:after {
border-top-color: rgba(255,255,255,0.95);
}
.flatpickr-time.hasSeconds .numInputWrapper {
width: 26%;
}
.flatpickr-time.time24hr .numInputWrapper {
width: 49%;
}
.flatpickr-time input {
background: transparent;
-webkit-box-shadow: none;
box-shadow: none;
border: 0;
border-radius: 0;
text-align: center;
margin: 0;
padding: 0;
height: inherit;
line-height: inherit;
color: rgba(255,255,255,0.95);
font-size: 14px;
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
}
.flatpickr-time input.flatpickr-hour {
font-weight: bold;
}
.flatpickr-time input.flatpickr-minute,
.flatpickr-time input.flatpickr-second {
font-weight: 400;
}
.flatpickr-time input:focus {
outline: 0;
border: 0;
}
.flatpickr-time .flatpickr-time-separator,
.flatpickr-time .flatpickr-am-pm {
height: inherit;
float: left;
line-height: inherit;
color: rgba(255,255,255,0.95);
font-weight: bold;
width: 2%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-align-self: center;
-ms-flex-item-align: center;
align-self: center;
}
.flatpickr-time .flatpickr-am-pm {
outline: 0;
width: 18%;
cursor: pointer;
text-align: center;
font-weight: 400;
}
.flatpickr-time input:hover,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:focus {
background: #6a7395;
}
.flatpickr-input[readonly] {
cursor: pointer;
}
@-webkit-keyframes fpFadeInDown {
from {
opacity: 0;
-webkit-transform: translate3d(0, -20px, 0);
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
@keyframes fpFadeInDown {
from {
opacity: 0;
-webkit-transform: translate3d(0, -20px, 0);
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
+13
View File
File diff suppressed because one or more lines are too long
+66 -11
View File
@@ -141,15 +141,15 @@ body, html {
.list-group-item-action {
transition: all 0.2s ease;
border: 1px solid rgba(74, 71, 163, 0.2);
background: rgba(30, 30, 50, 0.95); /* Slightly more opaque */
color: #ffffff; /* Bright white for main text */
background: rgba(30, 30, 50, 0.95); /* Slightly more opaque */
color: #ffffff; /* Bright white for main text */
}
.list-group-item-action:hover {
transform: translateY(-2px);
border-color: rgba(74, 71, 163, 0.4);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: rgba(40, 40, 60, 0.98); /* Even more opaque on hover */
background: rgba(40, 40, 60, 0.98); /* Even more opaque on hover */
}
.list-group-item {
@@ -317,29 +317,29 @@ body, html {
}
.list-group-item h5 .text-muted {
color: #bdbdbd !important; /* Lighter gray for year */
color: #bdbdbd !important; /* Lighter gray for year */
}
/* Description and other text */
.list-group-item .text-muted {
color: #bdbdbd !important; /* Lighter gray for better readability */
color: #bdbdbd !important; /* Lighter gray for better readability */
}
/* Meta information */
.list-group-item .small,
.list-group-item small {
color: #e0e0e0; /* Light gray for small text */
color: #e0e0e0; /* Light gray for small text */
}
/* Icons */
.list-group-item i {
color: #4a47a3; /* Accent color for icons */
color: #4a47a3; /* Accent color for icons */
}
/* Badge Styles - Improve readability */
.badge.bg-info {
background-color: rgba(74, 71, 163, 0.25) !important; /* Lighter background */
color: #ffffff !important; /* White text for contrast */
background-color: rgba(74, 71, 163, 0.25) !important; /* Lighter background */
color: #ffffff !important; /* White text for contrast */
border: 1px solid rgba(74, 71, 163, 0.4);
font-weight: 400;
}
@@ -383,7 +383,7 @@ body, html {
/* Meta sections */
.list-group-item .d-flex.align-items-center {
color: #e0e0e0; /* Light gray for meta information */
color: #e0e0e0; /* Light gray for meta information */
}
/* Content type labels (Comedy, Romance, etc.) */
@@ -399,7 +399,7 @@ body, html {
/* IMDb rating */
.text-warning {
color: #ffd700 !important; /* Brighter yellow for rating stars */
color: #ffd700 !important; /* Brighter yellow for rating stars */
}
.list-group-item img {
@@ -1024,6 +1024,10 @@ input[type="text"]:focus, input[type="password"]:focus, select.form-control:focu
color: #b0b0b0 !important;
}
.card-title {
color: #ffffff !important;
}
/* Description text */
.description-text {
color: #9e9e9e !important;
@@ -1099,6 +1103,25 @@ small.text-muted,
font-weight: 500;
}
#fileAnnotationList .card-subtitle {
flex-grow: 1;
margin-right: 1rem;
}
.excluded-file {
background-color: rgba(255,255,255,0.05);
padding: 0.5rem;
}
.form-switch .form-check-input {
cursor: pointer;
}
#file-inputs-container {
transition: opacity 0.3s ease-out;
}
/* Cards within modal */
.modal .card {
background: rgba(30, 30, 50, 0.9);
@@ -1143,6 +1166,7 @@ small.text-muted,
.modal .form-check-label {
color: #e0e0e0;
gap: 1rem;
}
/* Input groups */
@@ -1208,6 +1232,37 @@ small.text-muted,
transform: scale(1);
}
.spec-item {
cursor: pointer;
padding: 8px;
border-radius: 4px;
transition: all 0.2s ease;
position: relative;
}
.spec-item:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.spec-item::after {
content: '✎';
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
opacity: 0;
transition: opacity 0.2s ease;
}
.spec-item:hover::after {
opacity: 0.5;
}
.spec-item .spec-value {
padding-right: 20px;
}
@media (max-width: 576px) {
.sort-direction-toggle {
min-width: auto;
+404 -450
View File
@@ -10,6 +10,8 @@
<link href="/static/css/bootstrap-icons.css" rel="stylesheet">
<link href="/static/css/styles.css" rel="stylesheet">
<link href="/static/css/toastr.min.css" rel="stylesheet"/>
<link href="/static/css/flatpickr.min.css" rel="stylesheet">
<link href="/static/css/flatpickr.dark.css" rel="stylesheet">
<link rel="shortcut icon" href="{{ logo_url }}" type="image/x-icon">
</head>
@@ -38,388 +40,285 @@
<option value="update_images">Update Images</option>
</select>
</div>
</div>
<!-- Dynamic Scraper Parameters -->
<div id="scraperParameters">
<!-- Dynamic Scraper Parameters -->
<div id="scraperParameters">
<div id="quickImportParameters" style="display: none;">
<div class="section-container">
<h4 class="section-header">Quick Import</h4>
<hr class="section-divider">
<div id="quickImportParameters" style="display: none;">
<div class="section-container">
<h4 class="section-header">Quick Import</h4>
<hr class="section-divider">
<!-- Content Type Selection -->
<div class="mb-3">
<label for="quickMetaType" class="form-label">Content Type</label>
<select class="form-select" id="quickMetaType" name="quickMetaType" required>
<option value="movie">Movie</option>
<option value="series">Series</option>
<option value="sports">Sports</option>
</select>
<!-- Content Type Selection -->
<div class="mb-3">
<label for="quickMetaType" class="form-label">Content Type</label>
<select class="form-select" id="quickMetaType" name="quickMetaType" required>
<option value="movie">Movie</option>
<option value="series">Series</option>
<option value="sports">Sports</option>
</select>
</div>
<!-- Torrent Upload Area -->
<div class="card mb-4">
<div class="card-body">
<h6 class="card-subtitle mb-3 text-muted">Recommended: Torrent File Upload</h6>
<div class="mb-3">
<label for="quickTorrentFile" class="form-label">Torrent File</label>
<input type="file" class="form-control" id="quickTorrentFile"
name="quickTorrentFile" accept=".torrent"
onchange="toggleInput('quickMagnetLink', this)">
<div class="form-text">Upload a torrent file for better metadata extraction</div>
</div>
</div>
</div>
<!-- Torrent Upload Area -->
<div class="card mb-4">
<div class="card-body">
<h6 class="card-subtitle mb-3 text-muted">Recommended: Torrent File Upload</h6>
<div class="mb-3">
<label for="quickTorrentFile" class="form-label">Torrent File</label>
<input type="file" class="form-control" id="quickTorrentFile"
name="quickTorrentFile" accept=".torrent"
onchange="toggleInput('quickMagnetLink', this)">
<div class="form-text">Upload a torrent file for better metadata extraction</div>
<!-- Magnet Link Alternative -->
<div class="card mb-4">
<div class="card-body">
<h6 class="card-subtitle mb-3 text-muted">Alternative: Magnet Link</h6>
<div class="mb-3">
<label for="quickMagnetLink" class="form-label">Magnet Link</label>
<input type="text" class="form-control" id="quickMagnetLink"
name="quickMagnetLink" placeholder="magnet:?xt=urn:btih:..."
onchange="toggleInput('quickTorrentFile', this)">
<div class="form-text text-warning">
<i class="bi bi-exclamation-triangle"></i>
Note: Magnet links may have limited metadata availability
</div>
</div>
</div>
</div>
<!-- Magnet Link Alternative -->
<div class="card mb-4">
<div class="card-body">
<h6 class="card-subtitle mb-3 text-muted">Alternative: Magnet Link</h6>
<div class="mb-3">
<label for="quickMagnetLink" class="form-label">Magnet Link</label>
<input type="text" class="form-control" id="quickMagnetLink"
name="quickMagnetLink" placeholder="magnet:?xt=urn:btih:..."
onchange="toggleInput('quickTorrentFile', this)">
<div class="form-text text-warning">
<i class="bi bi-exclamation-triangle"></i>
Note: Magnet links may have limited metadata availability
</div>
<!-- Loading Indicator -->
<div id="analysisLoading" style="display: none;" class="text-center mb-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Analyzing...</span>
</div>
<p class="mt-2">Analyzing torrent and searching for matches...</p>
</div>
<!-- Results Container -->
<div id="matchResults" class="mb-4" style="display: none;">
<h5>Potential Matches</h5>
<div id="matchResultsContent" class="list-group">
<!-- Matches will be inserted here -->
</div>
<button type="button" class="btn btn-secondary mt-3 w-100"
onclick="switchToManualImport()">
None of these match? Import manually
</button>
</div>
</div>
</div>
<!-- Torrent Upload Parameters Section -->
<div id="torrentUploadParameters" style="display: none;">
<div class="section-container">
<!-- Basic Content Information -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Basic Information</h5>
</div>
<div class="card-body">
<!-- Content Type and Release Date -->
<div class="row mb-4">
<div class="col-md-4">
<label for="metaType" class="form-label">Content Type</label>
<select class="form-select" id="metaType" name="metaType" onchange="updateContentType()">
<option value="movie" {% if prefill_data.meta_type == 'movie' %}selected{% endif %}>Movie</option>
<option value="series" {% if prefill_data.meta_type == 'series' %}selected{% endif %}>Series</option>
<option value="sports">Sports</option>
</select>
</div>
<div class="col-md-4">
<label for="torrentType" class="form-label">Torrent Type</label>
<select class="form-select" id="torrentType" name="torrentType">
<option value="public">Public</option>
<option value="semi-private">Semi-Private</option>
<option value="private">Private</option>
<option value="web-seed">Web-Seed</option>
</select>
</div>
<div class="col-md-4">
<label for="createdAt" class="form-label">Release Date (DD/MM/YYYY)</label>
<input type="text"
class="form-control"
id="createdAt"
name="createdAt"
required
placeholder="dd/mm/yyyy">
</div>
</div>
<!-- Content Metadata -->
<div class="row mb-3">
<div class="col-md-4" id="metaIdContainer">
<label for="torrentImdbId" class="form-label">IMDb ID</label>
<input type="text" class="form-control" id="torrentImdbId" maxlength="20" placeholder="e.g., tt1234567"
value="{% if prefill_data.meta_id %}{{ prefill_data.meta_id }}{% endif %}">
</div>
<div class="col-md-8">
<label for="title" class="form-label">Title</label>
<alert class="alert alert-info py-1" role="alert">
<i class="bi bi-info circle"></i> Validate auto-filled data before submission
</alert>
<input type="text" class="form-control" id="title" placeholder="Content title">
</div>
</div>
<!-- Media URLs -->
<div class="row mb-3">
<div class="col-md-4">
<label for="poster" class="form-label">Poster URL</label>
<input type="url" class="form-control" id="poster">
</div>
<div class="col-md-4">
<label for="background" class="form-label">Background URL</label>
<input type="url" class="form-control" id="background">
</div>
<div class="col-md-4">
<label for="logo" class="form-label">Logo URL</label>
<input type="url" class="form-control" id="logo">
</div>
</div>
<!-- Add title to poster option -->
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="addTitleToPoster" name="addTitleToPoster">
<label class="form-check-label" for="addTitleToPoster">
Add title to poster
</label>
</div>
</div>
</div>
</div>
<!-- Loading Indicator -->
<div id="analysisLoading" style="display: none;" class="text-center mb-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Analyzing...</span>
</div>
<p class="mt-2">Analyzing torrent and searching for matches...</p>
<!-- Technical Specifications -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Technical Specifications</h5>
</div>
<div class="card-body">
<div class="technical-specs-basic"></div>
</div>
</div>
<!-- Results Container -->
<div id="matchResults" class="mb-4" style="display: none;">
<h5>Potential Matches</h5>
<div id="matchResultsContent" class="list-group">
<!-- Matches will be inserted here -->
</div>
<button type="button" class="btn btn-secondary mt-3 w-100"
onclick="switchToManualImport()">
None of these match? Import manually
</button>
<!-- Catalogs Section -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Content Categories</h5>
</div>
<div class="card-body">
<!-- Movie Catalogs -->
<div id="movieCatalogs" class="catalog-group" style="display: none;">
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i> Select categories to help organize your content
</div>
<div class="catalog-grid">
{% for catalog in supported_movie_catalog_ids %}
<div class="catalog-item">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="catalogs"
value="{{ catalog }}" id="catalog-{{ catalog }}">
<label class="form-check-label" for="catalog-{{ catalog }}">
{{ catalog_data.get(catalog) }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Series Catalogs -->
<div id="seriesCatalogs" class="catalog-group" style="display: none;">
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i> Select categories to help organize your content
</div>
<div class="catalog-grid">
{% for catalog in supported_series_catalog_ids %}
<div class="catalog-item">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="catalogs"
value="{{ catalog }}" id="catalog-{{ catalog }}">
<label class="form-check-label" for="catalog-{{ catalog }}">
{{ catalog_data.get(catalog) }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Sports Catalogs -->
<div id="sportsCatalogs" class="catalog-group" style="display: none;">
<div class="col-md-12">
<select class="form-select" id="sportsCatalog">
<option value="">Select Sports Category</option>
<option value="american_football">American Football / NFL</option>
<option value="baseball">Baseball / MLB</option>
<option value="basketball">Basketball / NBA</option>
<option value="football">Football / Soccer</option>
<option value="formula_racing">Formula Racing</option>
<option value="hockey">Hockey / NHL</option>
<option value="motogp_racing">MotoGP Racing</option>
<option value="other_sports">Other Sports</option>
<option value="rugby">Rugby / AFL</option>
<option value="wwe">WWE</option>
<option value="ufc">UFC</option>
<option value="fighting">Fighting</option>
</select>
</div>
</div>
</div>
</div>
<!-- Torrent Data Section -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Torrent Data</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="torrentFile" class="form-label">Torrent File</label>
<input type="file" class="form-control" id="torrentFile" accept=".torrent" onchange="toggleInput('magnetLink', this)">
</div>
<div class="mb-3">
<label for="magnetLink" class="form-label">Magnet Link</label>
<input type="text" class="form-control" id="magnetLink" placeholder="magnet:?xt=urn:btih:..." onchange="toggleInput('torrentFile', this)">
</div>
</div>
</div>
<!-- Uploader Information -->
<div class="mb-3">
<label for="uploaderName" class="form-label">Uploader Name (Optional)</label>
<input type="text" class="form-control" id="uploaderName" placeholder="Enter your name or leave blank for Anonymous">
</div>
</div>
<!-- Torrent Upload Parameters Section -->
<div id="torrentUploadParameters" style="display: none;">
<div class="section-container">
<!-- Basic Content Information -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Basic Information</h5>
</div>
<div class="card-body">
<!-- Content Type and Release Date -->
<div class="row mb-4">
<div class="col-md-4">
<label for="metaType" class="form-label">Content Type</label>
<select class="form-select" id="metaType" name="metaType" onchange="updateContentType()">
<option value="movie" {% if prefill_data.meta_type == 'movie' %}selected{% endif %}>Movie</option>
<option value="series" {% if prefill_data.meta_type == 'series' %}selected{% endif %}>Series</option>
<option value="sports">Sports</option>
</select>
</div>
<div class="col-md-4">
<label for="torrentType" class="form-label">Torrent Type</label>
<select class="form-select" id="torrentType" name="torrentType">
<option value="public">Public</option>
<option value="semi-private">Semi-Private</option>
<option value="private">Private</option>
<option value="web-seed">Web-Seed</option>
</select>
</div>
<div class="col-md-4">
<label for="createdAt" class="form-label">Release Date</label>
<input type="date"
class="form-control"
id="createdAt"
name="createdAt"
required
pattern="\d{2}/\d{2}/\d{4}"
placeholder="dd/mm/yyyy">
</div>
</div>
<!-- Content Metadata -->
<div class="row mb-3">
<div class="col-md-4" id="metaIdContainer">
<label for="torrentImdbId" class="form-label">IMDb ID</label>
<input type="text" class="form-control" id="torrentImdbId" maxlength="20" placeholder="e.g., tt1234567"
value="{% if prefill_data.meta_id %}{{ prefill_data.meta_id }}{% endif %}">
</div>
<div class="col-md-8">
<label for="title" class="form-label">Title</label>
<alert class="alert alert-info py-1" role="alert">
<i class="bi bi-info circle"></i> Validate auto-filled data before submission
</alert>
<input type="text" class="form-control" id="title" placeholder="Content title">
</div>
</div>
<!-- Media URLs -->
<div class="row mb-3">
<div class="col-md-4">
<label for="poster" class="form-label">Poster URL</label>
<input type="url" class="form-control" id="poster">
</div>
<div class="col-md-4">
<label for="background" class="form-label">Background URL</label>
<input type="url" class="form-control" id="background">
</div>
<div class="col-md-4">
<label for="logo" class="form-label">Logo URL</label>
<input type="url" class="form-control" id="logo">
</div>
</div>
<!-- Add title to poster option -->
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="addTitleToPoster" name="addTitleToPoster">
<label class="form-check-label" for="addTitleToPoster">
Add title to poster
</label>
</div>
</div>
</div>
</div>
<!-- Technical Specifications -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Technical Specifications</h5>
</div>
<div class="card-body">
<!-- Basic specs grid -->
<div class="technical-specs-basic"></div>
<!-- Advanced Toggle Button will be inserted here -->
<div class="advanced-toggle" id="toggleAdvanced">
<i class="bi bi-chevron-down"></i>
<span>Advanced Technical Options</span>
</div>
<!-- Advanced Technical Specifications -->
<div class="advanced-section" id="advancedSection">
<div class="row mb-4">
<div class="col-md-4">
<label for="resolution" class="form-label">Resolution</label>
<select class="form-select" id="resolution" onchange="updateSpecField('resolution', this.value)">
<option value="">Select Resolution</option>
<option value="480p">480p</option>
<option value="576p">576p</option>
<option value="720p">720p</option>
<option value="1080p">1080p</option>
<option value="1440p">1440p</option>
<option value="2160p">2160p</option>
<option value="4K">4K</option>
</select>
</div>
<div class="col-md-4">
<label for="quality" class="form-label">Quality</label>
<select class="form-select" id="quality" onchange="updateSpecField('quality', this.value)">
<option value="">Select Quality</option>
<option value="BluRay">BluRay</option>
<option value="BluRay REMUX">BluRay REMUX</option>
<option value="BRRip">BRRip</option>
<option value="BDRip">BDRip</option>
<option value="WEB-DL">WEB-DL</option>
<option value="HDRip">HDRip</option>
<option value="DVDRip">DVDRip</option>
<option value="HDTV">HDTV</option>
<option value="CAM">CAM</option>
<option value="TeleSync">TeleSync</option>
<option value="SCR">SCR</option>
</select>
</div>
<!-- Video Codec -->
<div id="video-codec-section" class="col-md-4">
<label for="videoCodec" class="form-label">Video Codec</label>
<select class="form-select" id="videoCodec" onchange="updateSpecField('codec', this.value)">
<option value="">Select Video Codec</option>
<option value="x264">x264</option>
<option value="x265">x265</option>
<option value="h.264">H.264</option>
<option value="h.265">H.265</option>
<option value="hevc">HEVC</option>
<option value="avc">AVC</option>
<option value="mpeg-2">MPEG-2</option>
<option value="mpeg-4">MPEG-4</option>
<option value="vp9">VP9</option>
</select>
</div>
</div>
<!-- Audio Codecs -->
<div id="audio-codecs-section">
<label class="form-label">Audio Codecs</label>
<div class="row">
{% for codec in ['AAC', 'AC3', 'DTS', 'DTS-HD MA', 'TrueHD', 'Atmos', 'DD+', 'Dolby Digital Plus', 'DTS Lossless'] %}
<div class="col-md-3 col-sm-6">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox"
name="audioCodecs" value="{{ codec }}"
id="audio-{{ codec|replace(' ', '')|replace('+', '') }}">
<label class="form-check-label"
for="audio-{{ codec|replace(' ', '')|replace('+', '') }}">
{{ codec }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- HDR Formats -->
<div id="hdr-formats-section">
<label class="form-label">HDR Formats</label>
<div class="row">
{% for format in ['DV', 'HDR10+', 'HDR', 'SDR'] %}
<div class="col-md-3 col-sm-6">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox"
name="hdrFormats" value="{{ format }}"
id="hdr-{{ format|replace('+', '') }}">
<label class="form-check-label"
for="hdr-{{ format|replace('+', '') }}">
{{ format }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Languages -->
<div id="languages-section">
<label class="form-label">Languages</label>
<div class="row">
{% for language in supported_languages %}
<div class="col-md-3 col-sm-6">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox"
name="languages" value="{{ language }}"
id="lang-{{ language }}">
<label class="form-check-label"
for="lang-{{ language }}">
{{ language }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<!-- Catalogs Section -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Content Categories</h5>
</div>
<div class="card-body">
<!-- Movie Catalogs -->
<div id="movieCatalogs" class="catalog-group" style="display: none;">
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i> Select categories to help organize your content
</div>
<div class="catalog-grid">
{% for catalog in supported_movie_catalog_ids %}
<div class="catalog-item">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="catalogs"
value="{{ catalog }}" id="catalog-{{ catalog }}">
<label class="form-check-label" for="catalog-{{ catalog }}">
{{ catalog_data.get(catalog) }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Series Catalogs -->
<div id="seriesCatalogs" class="catalog-group" style="display: none;">
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i> Select categories to help organize your content
</div>
<div class="catalog-grid">
{% for catalog in supported_series_catalog_ids %}
<div class="catalog-item">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="catalogs"
value="{{ catalog }}" id="catalog-{{ catalog }}">
<label class="form-check-label" for="catalog-{{ catalog }}">
{{ catalog_data.get(catalog) }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Sports Catalogs -->
<div id="sportsCatalogs" class="catalog-group" style="display: none;">
<div class="col-md-12">
<select class="form-select" id="sportsCatalog">
<option value="">Select Sports Category</option>
<option value="american_football">American Football / NFL</option>
<option value="baseball">Baseball / MLB</option>
<option value="basketball">Basketball / NBA</option>
<option value="football">Football / Soccer</option>
<option value="formula_racing">Formula Racing</option>
<option value="hockey">Hockey / NHL</option>
<option value="motogp_racing">MotoGP Racing</option>
<option value="other_sports">Other Sports</option>
<option value="rugby">Rugby / AFL</option>
<option value="wwe">WWE</option>
<option value="ufc">UFC</option>
<option value="fighting">Fighting</option>
</select>
</div>
</div>
</div>
</div>
<!-- Torrent Data Section -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Torrent Data</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="torrentFile" class="form-label">Torrent File</label>
<input type="file" class="form-control" id="torrentFile" accept=".torrent" onchange="toggleInput('magnetLink', this)">
</div>
<div class="mb-3">
<label for="magnetLink" class="form-label">Magnet Link</label>
<input type="text" class="form-control" id="magnetLink" placeholder="magnet:?xt=urn:btih:..." onchange="toggleInput('torrentFile', this)">
</div>
</div>
</div>
<!-- Uploader Information -->
<div class="mb-3">
<label for="uploaderName" class="form-label">Uploader Name (Optional)</label>
<input type="text" class="form-control" id="uploaderName" placeholder="Enter your name or leave blank for Anonymous">
<!-- Success Alert with Import New Button -->
<div class="alert alert-success alert-dismissible fade" role="alert" id="successAlert">
<div class="d-flex justify-content-between align-items-center">
<div>
<i class="bi bi-check-circle-fill me-2"></i>
<span id="successMessage">Torrent added successfully!</span>
</div>
<button type="button" class="btn btn-success btn-sm" onclick="switchToQuickImport()">
Import New Torrent
</button>
</div>
<button type="button" class="btn-close" onclick="hideSuccessAlert()"></button>
</div>
<!-- Scrapy Parameters -->
<div id="scrapyParameters" style="display: none;">
</div>
<!-- Scrapy Parameters -->
<div id="scrapyParameters" style="display: none;">
<div class="section-container">
<div class="mb-3">
<label for="spiderName" class="form-label">Spider Name</label>
<select class="form-select" id="spiderName" name="spider_name">
@@ -480,9 +379,13 @@
<input type="number" class="form-control" id="totalPages" name="total_pages" value="1">
<label><input type="checkbox" name="scrape_all" id="scrape_all"> Scrape All Pages</label>
</div>
</div>
<!-- TV Metadata Input -->
<div id="tvMetadataInput" style="display: none;">
</div>
<!-- TV Metadata Input -->
<div id="tvMetadataInput" style="display: none;">
<div class="section-container">
<h4 class="section-header">TV Metadata Input</h4>
<hr class="section-divider">
<div class="mb-3">
@@ -520,8 +423,13 @@
<div id="streamInputs"></div>
<button type="button" class="btn btn-primary" onclick="addStreamInput()">Add Stream</button>
</div>
</div>
<div id="m3uPlaylistInput" style="display: none;">
</div>
<div id="m3uPlaylistInput" style="display: none;">
<div class="section-container">
<div class="mb-3">
<div>
<label><input type="radio" name="m3uInputType" value="url" checked> M3U Playlist URL</label>
@@ -540,9 +448,14 @@
<label for="m3uPlaylistSource" class="form-label">Playlist Source Name</label>
<input type="text" class="form-control" id="m3uPlaylistSource" name="m3uPlaylistSource">
</div>
</div>
<!-- IMDb Data Update Parameters -->
<div id="imdbDataParameters" style="display: none;">
</div>
<!-- IMDb Data Update Parameters -->
<div id="imdbDataParameters" style="display: none;">
<div class="section-container">
<div class="mb-3">
<label for="imdbId" class="form-label">IMDb ID</label>
<input type="text" class="form-control" id="imdbId" name="imdb_id" placeholder="Enter IMDb ID (e.g., tt1234567)">
@@ -555,16 +468,22 @@
</select>
</div>
</div>
</div>
<div id="blockTorrentParameters" style="display: none;">
<div class="section-container">
<div id="blockTorrentParameters" style="display: none;">
<div class="mb-3">
<label for="blockTorrentInfoHash" class="form-label">Torrent Info Hash</label>
<input type="text" class="form-control" id="blockTorrentInfoHash" name="blockTorrentInfoHash" required
value="{% if prefill_data.info_hash %}{{ prefill_data.info_hash }}{% endif %}">
</div>
</div>
</div>
<div id="migrationParameters" style="display: none;">
<div class="section-container">
<div id="migrationParameters" style="display: none;">
<div class="mb-3">
<label for="mediafusionId" class="form-label">MediaFusion ID</label>
<input type="text" class="form-control" id="mediafusionId" name="mediafusion_id" required
@@ -583,99 +502,99 @@
</select>
</div>
</div>
</div>
<div id="updateImagesParameters" style="display: none;">
<div class="section-container">
<h4 class="section-header">Update Images</h4>
<hr class="section-divider">
<div id="updateImagesParameters" style="display: none;">
<div class="section-container">
<h4 class="section-header">Update Images</h4>
<hr class="section-divider">
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i>
Provide the content ID and at least one image URL to update
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i>
Provide the content ID and at least one image URL to update
</div>
<!-- Content ID Input -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Content Information</h5>
</div>
<!-- Content ID Input -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Content Information</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="imageUpdateMetaId" class="form-label">Content ID *</label>
<input type="text"
class="form-control"
id="imageUpdateMetaId"
name="meta_id"
required
placeholder="Enter IMDb ID (tt1234567) or MediaFusion ID">
<div class="form-text">
<i class="bi bi-info-circle-fill"></i>
This can be either an IMDb ID (starts with 'tt') or a MediaFusion ID (starts with 'mf')
</div>
</div>
</div>
</div>
<!-- Image URLs Card -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Image URLs</h5>
<small class="text-muted">At least one image URL must be provided</small>
</div>
<div class="card-body">
<!-- Poster URL -->
<div class="mb-4">
<label for="imageUpdatePoster" class="form-label">Poster URL</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-image"></i>
</span>
<input type="url"
class="form-control"
id="imageUpdatePoster"
name="poster"
placeholder="https://example.com/poster.jpg">
</div>
<div class="form-text">High-quality vertical image for the content poster</div>
</div>
<!-- Background URL -->
<div class="mb-4">
<label for="imageUpdateBackground" class="form-label">Background URL</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-image-fill"></i>
</span>
<input type="url"
class="form-control"
id="imageUpdateBackground"
name="background"
placeholder="https://example.com/background.jpg">
</div>
<div class="form-text">Wide image to be used as background/backdrop</div>
</div>
<!-- Logo URL -->
<div class="mb-3">
<label for="imageUpdateLogo" class="form-label">Logo URL</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-vector-pen"></i>
</span>
<input type="url"
class="form-control"
id="imageUpdateLogo"
name="logo"
placeholder="https://example.com/logo.png">
</div>
<div class="form-text">Transparent PNG logo of the content</div>
<div class="card-body">
<div class="mb-3">
<label for="imageUpdateMetaId" class="form-label">Content ID *</label>
<input type="text"
class="form-control"
id="imageUpdateMetaId"
name="meta_id"
required
placeholder="Enter IMDb ID (tt1234567) or MediaFusion ID">
<div class="form-text">
<i class="bi bi-info-circle-fill"></i>
This can be either an IMDb ID (starts with 'tt') or a MediaFusion ID (starts with 'mf')
</div>
</div>
</div>
</div>
</div>
<!-- Image URLs Card -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Image URLs</h5>
<small class="text-muted">At least one image URL must be provided</small>
</div>
<div class="card-body">
<!-- Poster URL -->
<div class="mb-4">
<label for="imageUpdatePoster" class="form-label">Poster URL</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-image"></i>
</span>
<input type="url"
class="form-control"
id="imageUpdatePoster"
name="poster"
placeholder="https://example.com/poster.jpg">
</div>
<div class="form-text">High-quality vertical image for the content poster</div>
</div>
<!-- Background URL -->
<div class="mb-4">
<label for="imageUpdateBackground" class="form-label">Background URL</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-image-fill"></i>
</span>
<input type="url"
class="form-control"
id="imageUpdateBackground"
name="background"
placeholder="https://example.com/background.jpg">
</div>
<div class="form-text">Wide image to be used as background/backdrop</div>
</div>
<!-- Logo URL -->
<div class="mb-3">
<label for="imageUpdateLogo" class="form-label">Logo URL</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-vector-pen"></i>
</span>
<input type="url"
class="form-control"
id="imageUpdateLogo"
name="logo"
placeholder="https://example.com/logo.png">
</div>
<div class="form-text">Transparent PNG logo of the content</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- API Password Configuration -->
@@ -773,11 +692,46 @@
</div>
</div>
<!-- Spec Selection Modal -->
<div class="modal fade" id="specSelectionModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="specModalTitle">Select Specification</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Single Selection -->
<div id="singleSpecSelection" style="display: none;">
<select class="form-select mb-3" id="specSelectSingle">
<!-- Options will be populated dynamically -->
</select>
</div>
<!-- Multiple Selection -->
<div id="multipleSpecSelection" style="display: none;">
<div class="spec-checkboxes mb-3">
<!-- Checkboxes will be populated dynamically -->
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirmSpecSelection">Apply</button>
</div>
</div>
</div>
</div>
<input type="hidden" id="supportedLanguages" value='{{ supported_languages|tojson|safe }}'>
<!-- JS for Bootstrap -->
<script src="/static/js/jquery-3.6.0.min.js"></script>
<script src="/static/js/popper.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/toastr.min.js"></script>
<script src="/static/js/flatpickr.js"></script>
<script src="/static/js/scraperControl.js"></script>
</body>
File diff suppressed because one or more lines are too long
+411 -193
View File
@@ -1,3 +1,57 @@
// Define spec options
const SPEC_OPTIONS = {
resolution: [
{value: '480p', label: '480p (SD)'},
{value: '576p', label: '576p (SD)'},
{value: '720p', label: '720p (HD)'},
{value: '1080p', label: '1080p (Full HD)'},
{value: '1440p', label: '1440p (2K)'},
{value: '2160p', label: '2160p (4K)'},
{value: '4K', label: '4K UHD'}
],
quality: [
{value: 'BluRay', label: 'BluRay'},
{value: 'BluRay REMUX', label: 'BluRay REMUX'},
{value: 'BRRip', label: 'BRRip'},
{value: 'BDRip', label: 'BDRip'},
{value: 'WEB-DL', label: 'WEB-DL'},
{value: 'HDRip', label: 'HDRip'},
{value: 'DVDRip', label: 'DVDRip'},
{value: 'HDTV', label: 'HDTV'},
{value: 'CAM', label: 'CAM'},
{value: 'TeleSync', label: 'TeleSync'},
{value: 'SCR', label: 'SCR'}
],
codec: [
{value: 'x264', label: 'x264'},
{value: 'x265', label: 'x265 (HEVC)'},
{value: 'h.264', label: 'H.264 (AVC)'},
{value: 'h.265', label: 'H.265 (HEVC)'},
{value: 'hevc', label: 'HEVC'},
{value: 'avc', label: 'AVC'},
{value: 'mpeg-2', label: 'MPEG-2'},
{value: 'mpeg-4', label: 'MPEG-4'},
{value: 'vp9', label: 'VP9'}
],
audio: [
{value: 'AAC', label: 'AAC'},
{value: 'AC3', label: 'AC3 (Dolby Digital)'},
{value: 'DTS', label: 'DTS'},
{value: 'DTS-HD MA', label: 'DTS-HD MA'},
{value: 'TrueHD', label: 'Dolby TrueHD'},
{value: 'Atmos', label: 'Dolby Atmos'},
{value: 'DD+', label: 'DD+'},
{value: 'Dolby Digital Plus', label: 'Dolby Digital Plus'},
{value: 'DTS Lossless', label: 'DTS Lossless'}
],
hdr: [
{value: 'DV', label: 'Dolby Vision'},
{value: 'HDR10+', label: 'HDR10+'},
{value: 'HDR', label: 'HDR'},
{value: 'SDR', label: 'SDR'}
]
};
// ---- Helper Functions ----
function showNotification(message, type = 'info') {
@@ -315,10 +369,28 @@ function showFileAnnotationModal(files) {
files.forEach((file, index) => {
const fileRow = `
<div class="card mb-3">
<div class="card mb-3" id="file-row-${index}">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted">${file.filename}</h6>
<div class="row">
<div class="d-flex justify-content-between align-items-start mb-2">
<div class="d-flex align-items-center flex-grow-1">
<!-- Include/Exclude Toggle -->
<div class="form-check form-switch me-2">
<input class="form-check-input" type="checkbox"
id="include-file-${index}"
checked
onchange="toggleFileInclusion(${index})">
<label class="form-check-label" for="include-file-${index}">
Include
</label>
</div>
<h6 class="card-subtitle mb-0 text-muted text-truncate"
style="max-width: 80%;"
title="${file.filename}">
${file.filename}
</h6>
</div>
</div>
<div class="row" id="file-inputs-${index}">
<div class="col-md-6">
<label class="form-label">Season</label>
<input type="number" class="form-control season-input"
@@ -359,28 +431,38 @@ function showFileAnnotationModal(files) {
</div>
<div class="mb-2">
<label class="form-label">Release Date</label>
<input type="date" class="form-control"
id="release-${index}">
<input type="text" class="form-control"
id="release-${index}"
placeholder="DD/MM/YYYY">
</div>
</div>
` : `
`}
` : ''}
</div>
</div>`;
fileList.insertAdjacentHTML('beforeend', fileRow);
});
files.forEach((file, index) => {
// Initialize date picker for each file
setupDateInput(`release-${index}`, false);
});
// Set up bulk season assignment handler
document.getElementById('applyBulkSeason').onclick = () => {
const season = document.getElementById('bulkSeason').value;
if (season) {
document.querySelectorAll('.season-input').forEach(input => {
input.value = season;
document.querySelectorAll('.season-input:not([disabled])').forEach(input => {
// Only apply to included files
const index = input.dataset.index;
const isIncluded = document.getElementById(`include-file-${index}`)?.checked;
if (isIncluded) {
input.value = season;
}
});
}
};
// Set up multiple seasons handler
// Set up multiple seasons handler with the same included files logic
document.getElementById('applyMultiSeasons').onclick = () => {
const seasonsInput = document.getElementById('multipleSeasons').value;
if (!seasonsInput) return;
@@ -395,7 +477,13 @@ function showFileAnnotationModal(files) {
const distribution = document.querySelector('input[name="seasonDistribution"]:checked').value;
const episodesPerSeason = parseInt(document.getElementById('episodeCount').value) || 0;
const seasonInputs = document.querySelectorAll('.season-input');
// Filter for only included files
const seasonInputs = Array.from(document.querySelectorAll('.season-input:not([disabled])'))
.filter(input => {
const index = input.dataset.index;
return document.getElementById(`include-file-${index}`)?.checked;
});
if (distribution === 'auto') {
// Distribute episodes evenly across seasons
const filesPerSeason = Math.ceil(seasonInputs.length / seasons.length);
@@ -414,7 +502,7 @@ function showFileAnnotationModal(files) {
}
};
// Set up distribution method handlers
// Distribution method handlers
document.querySelectorAll('input[name="seasonDistribution"]').forEach(radio => {
radio.onchange = () => {
document.getElementById('episodesPerSeason').style.display =
@@ -431,7 +519,6 @@ function showFileAnnotationModal(files) {
}
};
// Initial application
applySeasons();
};
@@ -440,24 +527,28 @@ function showFileAnnotationModal(files) {
return new Promise((resolve, reject) => {
document.getElementById('confirmAnnotation').onclick = () => {
const annotatedFiles = files.map((file, index) => {
const baseData = {
...file,
season_number: parseInt(document.getElementById(`season-${index}`).value) || null,
episode_number: parseInt(document.getElementById(`episode-${index}`).value) || null,
};
if (isSportsContent) {
return {
...baseData,
title: document.getElementById(`title-${index}`).value || null,
overview: document.getElementById(`overview-${index}`).value || null,
thumbnail: document.getElementById(`thumbnail-${index}`).value || null,
release_date: document.getElementById(`release-${index}`).value || null,
const annotatedFiles = [];
files.forEach((file, index) => {
// Only include files that are marked for inclusion
if (document.getElementById(`include-file-${index}`)?.checked) {
const baseData = {
...file,
season_number: parseInt(document.getElementById(`season-${index}`).value) || null,
episode_number: parseInt(document.getElementById(`episode-${index}`).value) || null,
};
if (isSportsContent) {
annotatedFiles.push({
...baseData,
title: document.getElementById(`title-${index}`).value || null,
overview: document.getElementById(`overview-${index}`).value || null,
thumbnail: document.getElementById(`thumbnail-${index}`).value || null,
release_date: document.getElementById(`release-${index}`).value || null,
});
} else {
annotatedFiles.push(baseData);
}
}
return baseData;
});
bsModal.hide();
resolve(annotatedFiles);
@@ -469,6 +560,33 @@ function showFileAnnotationModal(files) {
});
}
// Function to toggle file inclusion
function toggleFileInclusion(index) {
const checkbox = document.getElementById(`include-file-${index}`);
const inputsContainer = document.getElementById(`file-inputs-${index}`);
const fileRow = document.getElementById(`file-row-${index}`);
if (checkbox.checked) {
// File is included
inputsContainer.style.opacity = '1';
inputsContainer.style.pointerEvents = 'auto';
fileRow.classList.remove('excluded-file');
// Enable all inputs in the container
inputsContainer.querySelectorAll('input, textarea').forEach(input => {
input.disabled = false;
});
} else {
// File is excluded
inputsContainer.style.opacity = '0.5';
inputsContainer.style.pointerEvents = 'none';
fileRow.classList.add('excluded-file');
// Disable all inputs in the container
inputsContainer.querySelectorAll('input, textarea').forEach(input => {
input.disabled = true;
});
}
}
function toggleInput(disableId, input) {
document.getElementById(disableId).disabled = !!input.value;
@@ -557,38 +675,28 @@ function handleInitialSetup() {
updateFormFields();
}
function setupDateInput(inputId, defaultToToday = true) {
const dateInput = document.getElementById(inputId);
if (!dateInput) return;
if (defaultToToday) {
// Set default value to today's date
const today = new Date();
const day = String(today.getDate()).padStart(2, '0');
const month = String(today.getMonth() + 1).padStart(2, '0');
const year = today.getFullYear();
dateInput.value = `${year}-${month}-${day}`;
}
// Format date for display
dateInput.addEventListener('input', function(e) {
const date = new Date(this.value);
if (!isNaN(date)) {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
this.title = `${day}/${month}/${year}`;
}
function setupDateInput(inputId, defaultToToday = false, initialDate = null) {
const fp = flatpickr(`#${inputId}`, {
dateFormat: "d/m/Y",
altInput: true,
altFormat: "d/m/Y",
allowInput: true,
defaultHour: 12,
maxDate: "today",
locale: {
firstDayOfWeek: 1
},
theme: "dark",
defaultDate: initialDate // Set initial date if provided
});
// Trigger initial formatting
if (dateInput.value) {
const event = new Event('input');
dateInput.dispatchEvent(event);
if (defaultToToday && !initialDate) {
const today = new Date();
fp.setDate(today);
}
}
function constructTvMetadata() {
// Basic TV Metadata collection
let tvMetaData = {
@@ -861,29 +969,174 @@ function updateBasicTechnicalSpecs(torrentData = {}) {
];
specsContainer.innerHTML = specs.map(spec => `
<div class="spec-item">
<div class="spec-item" data-spec-type="${spec.type}">
<i class="bi ${spec.icon}"></i>
<div>
<div class="spec-label">${spec.label}</div>
<div class="spec-value" id="${spec.type}Spec" >${formatTechnicalSpec(spec.value, spec.type)}</div>
<div class="spec-value" id="${spec.type}Spec">${formatTechnicalSpec(spec.value, spec.type)}</div>
</div>
</div>
`).join('');
}
// Function to set up the advanced toggle system
function setupAdvancedToggle() {
document.getElementById('toggleAdvanced').addEventListener('click', () => {
const toggleButton = document.getElementById('toggleAdvanced');
toggleButton.classList.toggle('active');
const advancedSection = document.getElementById('advancedSection');
advancedSection.classList.toggle('show');
// Add click handlers to each spec item
specsContainer.querySelectorAll('.spec-item').forEach(item => {
item.addEventListener('click', function () {
const specType = this.dataset.specType;
const modal = new bootstrap.Modal(document.getElementById('specSelectionModal'));
const modalTitle = document.getElementById('specModalTitle');
const singleSelection = document.getElementById('singleSpecSelection');
const multipleSelection = document.getElementById('multipleSpecSelection');
// Determine if this is a multi-select specification
const isMultiSelect = ['audio', 'hdr', 'languages'].includes(specType);
// Update modal title
modalTitle.textContent = `Select ${this.querySelector('.spec-label').textContent}`;
// Show appropriate selection interface
singleSelection.style.display = isMultiSelect ? 'none' : 'block';
multipleSelection.style.display = isMultiSelect ? 'block' : 'none';
// Get current value
const currentValue = this.querySelector('.spec-value').textContent;
// Populate options based on spec type
if (isMultiSelect) {
populateMultiSelect(specType, currentValue);
} else {
populateSingleSelect(specType, currentValue);
}
// Set up confirmation handler
document.getElementById('confirmSpecSelection').onclick = () => {
let selectedValue;
if (isMultiSelect) {
selectedValue = Array.from(document.querySelectorAll('.spec-checkboxes input:checked'))
.map(cb => cb.value)
.join(', ');
} else {
selectedValue = document.getElementById('specSelectSingle').value;
}
if (selectedValue) {
// Update the spec display
updateSpecField(specType, selectedValue);
}
modal.hide();
};
modal.show();
});
});
// Initial update of basic specs
updateBasicTechnicalSpecs();
}
// Update populateSingleSelect to use new options format
function populateSingleSelect(specType, currentValue) {
const select = document.getElementById('specSelectSingle');
select.innerHTML = '<option value="">Select an option</option>';
const options = getOptionsForSpecType(specType);
options.forEach(option => {
const opt = document.createElement('option');
opt.value = option.value;
opt.textContent = option.label;
opt.selected = currentValue === option.value;
select.appendChild(opt);
});
}
// Update populateMultiSelect to use new options format
function populateMultiSelect(specType, currentValue) {
const container = document.querySelector('.spec-checkboxes');
container.innerHTML = '';
const currentValues = currentValue.split(', ').map(v => v.trim());
const options = getOptionsForSpecType(specType);
options.forEach(option => {
const div = document.createElement('div');
div.className = 'form-check';
div.innerHTML = `
<input class="form-check-input" type="checkbox" value="${option.value}"
id="spec-${option.value.replace(/[^a-zA-Z0-9]/g, '')}"
${currentValues.includes(option.value) ? 'checked' : ''}>
<label class="form-check-label" for="spec-${option.value.replace(/[^a-zA-Z0-9]/g, '')}">
${option.label}
</label>
`;
container.appendChild(div);
});
}
// Helper function to get current spec values
function getSpecValues() {
return {
resolution: document.getElementById('resolutionSpec')?.textContent.trim(),
quality: document.getElementById('qualitySpec')?.textContent.trim(),
codec: document.getElementById('codecSpec')?.textContent.trim(),
audio: document.getElementById('audioSpec')?.textContent.trim(),
languages: document.getElementById('languagesSpec')?.textContent.trim(),
hdr: document.getElementById('hdrSpec')?.textContent.trim()
};
}
// Helper function to get options for a spec type
function getOptionsForSpecType(specType) {
if (specType === 'languages') {
// Get languages from hidden input
try {
const supportedLanguages = JSON.parse(document.getElementById('supportedLanguages').value);
return supportedLanguages.map(lang => ({value: lang, label: lang}));
} catch (e) {
console.error('Error parsing supported languages:', e);
return [];
}
}
return SPEC_OPTIONS[specType] || [];
}
function switchToQuickImport() {
document.getElementById('scraperSelect').value = 'quick_import';
updateFormFields();
formUtils.resetForm();
hideSuccessAlert();
window.scrollTo(0, 0);
}
// Function to show success message with Import New button
function showSuccessWithImportNew(message) {
const alert = document.getElementById('successAlert');
const messageElement = document.getElementById('successMessage');
// If alert doesn't exist, use notification
if (!alert) {
showNotification(message, 'success');
return;
}
messageElement.textContent = message;
alert.style.display = 'block';
alert.classList.add('show');
// Scroll to the top to make the alert visible
window.scrollTo(0, 0);
}
function hideSuccessAlert() {
const alert = document.getElementById('successAlert');
if (alert) {
alert.classList.remove('show');
alert.style.display = 'none';
// Clear the message
const messageElement = document.getElementById('successMessage');
if (messageElement) {
messageElement.textContent = '';
}
}
}
function updateSpecField(fieldId, value) {
const targetInput = document.getElementById(`${fieldId}Spec`);
if (targetInput) {
@@ -984,35 +1237,6 @@ const formUtils = {
}
},
setCheckboxValues(items, prefix) {
if (!items) return;
const values = Array.isArray(items) ? items : items.split(',');
values.forEach(value => {
const checkboxId = `${prefix}-${value.replace(/[^a-zA-Z0-9]/g, '')}`;
const checkbox = document.getElementById(checkboxId);
if (checkbox) checkbox.checked = true;
});
},
setCatalogs(type, catalogs) {
if (!type || !catalogs) return;
const container = document.getElementById(`${type}Catalogs`);
if (!container) return;
// Clear existing selections
container.querySelectorAll('input[type="checkbox"]')
.forEach(cb => cb.checked = false);
// Set new selections
const catalogList = Array.isArray(catalogs) ? catalogs : catalogs.split(',');
catalogList.forEach(cat => {
const checkbox = container.querySelector(`input[value="${cat}"]`);
if (checkbox) checkbox.checked = true;
});
},
transferTorrentFile() {
const quickFile = document.getElementById('quickTorrentFile');
const torrentFile = document.getElementById('torrentFile');
@@ -1083,48 +1307,81 @@ function switchToManualImport(torrentData) {
function applyTorrentData(torrentData) {
if (!torrentData) return;
// Update basic specs display
updateBasicTechnicalSpecs(torrentData);
// check title and if its empty then add title from torrentData
if (!document.getElementById('title').value) {
// Update title if empty
if (!document.getElementById('title').value && torrentData.title) {
document.getElementById('title').value = torrentData.title;
}
// Set release date
if (torrentData.created_at) {
const date = new Date(torrentData.created_at);
document.getElementById('createdAt').value = date.toISOString().split('T')[0];
setupDateInput('createdAt', false, date);
}
// Set technical specs
const technicalFields = {
'resolution': 'resolution',
'quality': 'quality',
'codec': 'videoCodec'
// Map torrent data fields to spec types
const specMapping = {
resolution: torrentData.resolution,
quality: torrentData.quality,
codec: torrentData.codec,
audio: torrentData.audio,
hdr: torrentData.hdr,
languages: torrentData.languages
};
Object.entries(technicalFields).forEach(([dataKey, fieldId]) => {
if (torrentData[dataKey]) {
document.getElementById(fieldId).value = torrentData[dataKey];
// Helper function to format value based on spec type
function formatSpecValue(type, value) {
if (!value) return 'Not Set';
// For array-like values (could be array or comma-separated string)
if (typeof value === 'string' && value.includes(',') || Array.isArray(value)) {
const valueArray = Array.isArray(value) ? value : value.split(',').map(v => v.trim());
return valueArray.join(', ');
}
// For single values, validate against available options
const options = getOptionsForSpecType(type);
const option = options.find(opt => opt.value.toLowerCase() === value.toLowerCase());
return option ? option.value : value;
}
// Update basic specs display
Object.entries(specMapping).forEach(([type, value]) => {
const formattedValue = formatSpecValue(type, value);
updateSpecField(type, formattedValue);
});
// Set checkbox-based fields
formUtils.setCheckboxValues(torrentData.audio, 'audio');
formUtils.setCheckboxValues(torrentData.hdr, 'hdr');
formUtils.setCheckboxValues(torrentData.languages, 'lang');
// Update basic technical specs display
updateBasicTechnicalSpecs({
resolution: specMapping.resolution,
quality: specMapping.quality,
codec: specMapping.codec,
audio: specMapping.audio,
hdr: specMapping.hdr,
languages: specMapping.languages
});
// Handle catalogs
// Handle content type specific settings
document.getElementById('metaType').value = torrentData.type;
if (torrentData.type === 'movie' || torrentData.type === 'series') {
formUtils.setCatalogs(torrentData.type, torrentData.catalog);
if (torrentData.catalog) {
const catalogs = Array.isArray(torrentData.catalog)
? torrentData.catalog
: torrentData.catalog.split(',').map(c => c.trim());
const container = document.getElementById(`${torrentData.type}Catalogs`);
if (container) {
catalogs.forEach(catalog => {
const checkbox = container.querySelector(`input[value="${catalog}"]`);
if (checkbox) checkbox.checked = true;
});
}
}
}
// Transfer torrent file or magnet link
formUtils.transferTorrentFile();
// Set metaType
document.getElementById('metaType').value = torrentData.type;
// Update UI to reflect content type
updateContentType();
}
@@ -1134,76 +1391,42 @@ async function handleAddTorrent(submitBtn, loadingSpinner, forceImport = false,
const torrentType = document.getElementById('torrentType').value;
const isSportsContent = metaType === 'sports';
// Handle content metadata
const metaId = document.getElementById('torrentImdbId')?.value;
const title = document.getElementById('title').value;
const poster = document.getElementById('poster').value;
const background = document.getElementById('background').value;
const logo = document.getElementById('logo').value;
const createdAt = document.getElementById('createdAt').value;
const uploaderName = document.getElementById('uploaderName').value.trim() || 'Anonymous';
// Validate required fields
if (isSportsContent && !title) {
showNotification('Title is required for sports content.', 'error');
resetButton(submitBtn, loadingSpinner);
return;
}
if (!createdAt) {
showNotification('Created At is required.', 'error');
resetButton(submitBtn, loadingSpinner);
return;
}
// Get current spec values
const specValues = getSpecValues();
// Add basic metadata to formData
formData.append('meta_type', metaType);
formData.append('torrent_type', torrentType);
formData.append('created_at', createdAt);
formData.append('uploader', uploaderName);
formData.append('created_at', document.getElementById('createdAt').value);
formData.append('uploader', document.getElementById('uploaderName').value.trim() || 'Anonymous');
if (!isSportsContent && metaId) {
formData.append('meta_id', metaId);
// Add content metadata
if (!isSportsContent && document.getElementById('torrentImdbId')?.value) {
formData.append('meta_id', document.getElementById('torrentImdbId').value);
}
// Add optional metadata fields if provided
const title = document.getElementById('title').value;
if (!title && isSportsContent) {
showNotification('Title is required for sports content.', 'error');
resetButton(submitBtn, loadingSpinner);
return;
}
if (title) formData.append('title', title);
if (poster) formData.append('poster', poster);
if (background) formData.append('background', background);
if (logo) formData.append('logo', logo);
// Add optional metadata
['poster', 'background', 'logo'].forEach(field => {
const value = document.getElementById(field)?.value;
if (value) formData.append(field, value);
});
// Add technical specifications
const resolution = document.getElementById('resolution').value;
const quality = document.getElementById('quality').value;
const videoCodec = document.getElementById('videoCodec').value;
if (resolution) formData.append('resolution', resolution);
if (quality) formData.append('quality', quality);
if (videoCodec) formData.append('codec', videoCodec);
// Handle multiple audio codecs
const selectedAudioCodecs = Array.from(
document.querySelectorAll('input[name="audioCodecs"]:checked')
).map(el => el.value);
if (selectedAudioCodecs.length > 0) {
formData.append('audio', selectedAudioCodecs.join(','));
}
// Handle HDR formats
const selectedHdrFormats = Array.from(
document.querySelectorAll('input[name="hdrFormats"]:checked')
).map(el => el.value);
if (selectedHdrFormats.length > 0) {
formData.append('hdr', selectedHdrFormats.join(','));
}
// Handle languages
const selectedLanguages = Array.from(document.querySelectorAll('input[name="languages"]:checked'))
.map(el => el.value);
if (selectedLanguages.length > 0) {
formData.append('languages', selectedLanguages.join(','));
}
Object.entries(specValues).forEach(([key, value]) => {
if (value && value !== 'Not Set') {
formData.append(key, value);
}
});
// Handle catalogs
if (isSportsContent) {
const sportsCatalog = document.getElementById('sportsCatalog').value;
if (!sportsCatalog) {
@@ -1213,7 +1436,6 @@ async function handleAddTorrent(submitBtn, loadingSpinner, forceImport = false,
}
formData.append('catalogs', sportsCatalog);
} else {
// Handle movie/series catalogs
const catalogInputs = document.querySelectorAll(`#${metaType}Catalogs input[name="catalogs"]:checked`);
const catalogs = Array.from(catalogInputs).map(el => el.value);
if (catalogs.length > 0) {
@@ -1221,8 +1443,9 @@ async function handleAddTorrent(submitBtn, loadingSpinner, forceImport = false,
}
}
const addTitleToPoster = document.getElementById('addTitleToPoster').checked;
formData.append('is_add_title_to_poster', addTitleToPoster.toString());
// Handle add title to poster option
formData.append('is_add_title_to_poster',
document.getElementById('addTitleToPoster').checked.toString());
// Handle torrent file/magnet
const magnetLink = document.getElementById('magnetLink').value;
@@ -1246,16 +1469,11 @@ async function handleAddTorrent(submitBtn, loadingSpinner, forceImport = false,
formData.append('torrent_file', torrentFile);
}
// Add force import flag if needed
if (forceImport) {
formData.append('force_import', 'true');
}
// Add annotated files if available
if (annotatedFiles) {
formData.append('file_data', JSON.stringify(annotatedFiles));
}
// Add force import and annotated files if present
if (forceImport) formData.append('force_import', 'true');
if (annotatedFiles) formData.append('file_data', JSON.stringify(annotatedFiles));
// Submit the form
try {
const response = await fetch('/scraper/torrent', {
method: 'POST',
@@ -1287,7 +1505,7 @@ async function handleAddTorrent(submitBtn, loadingSpinner, forceImport = false,
} else if (data.detail) {
showNotification(data.detail, 'error');
} else {
showNotification(data.status, 'success');
showSuccessWithImportNew(data.status);
}
} catch (error) {
console.error('Error submitting torrent:', error);
@@ -1602,8 +1820,8 @@ async function submitScraperForm() {
// Initial update for form fields on page load
document.addEventListener('DOMContentLoaded', function () {
handleInitialSetup();
setupAdvancedToggle();
updateBasicTechnicalSpecs();
setupFieldChangeHandlers();
setupDateInput('createdAt');
setupDateInput('createdAt', true);
});
document.getElementById('spiderName').addEventListener('change', toggleSpiderSpecificFields);
+3 -2
View File
@@ -263,7 +263,8 @@ async def add_torrent(
await torrent_stream.delete()
else:
return {
"status": f"Torrent already exists and attached to meta id: {torrent_stream.meta_id}"
"status": f"⚠️ Torrent {info_hash} already exists and is attached to meta ID: {torrent_stream.meta_id}. "
f"Thank you for trying to contribute ✨. If the torrent is not visible, please contact support with the Torrent InfoHash."
}
# Add technical specifications to torrent_data
@@ -524,7 +525,7 @@ async def add_torrent(
)
return {
"status": f"Successfully added torrent: {torrent_stream.id} for {title}. Thanks for your contribution."
"status": f"🎉 Successfully added torrent: {torrent_stream.id} for {title} ({torrent_stream.meta_id}). Thanks for your contribution! 🙌"
}