mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-12-16 11:37:58 +02:00
Added iCal feed for the calendar, reachable through
/feed/calendar/NzbDrone.ics or through the calendar page.
This commit is contained in:
parent
7445adb455
commit
cf1e0a4946
69
src/NzbDrone.Api/Calendar/CalendarFeedModule.cs
Normal file
69
src/NzbDrone.Api/Calendar/CalendarFeedModule.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using Nancy;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using DDay.iCal;
|
||||
using NzbDrone.Core.Tv;
|
||||
using Nancy.Responses;
|
||||
|
||||
namespace NzbDrone.Api.Calendar
|
||||
{
|
||||
public class CalendarFeedModule : NzbDroneFeedModule
|
||||
{
|
||||
private readonly IEpisodeService _episodeService;
|
||||
|
||||
public CalendarFeedModule(IEpisodeService episodeService)
|
||||
: base("calendar")
|
||||
{
|
||||
_episodeService = episodeService;
|
||||
|
||||
Get["/NzbDrone.ics"] = options => GetCalendarFeed();
|
||||
}
|
||||
|
||||
private Response GetCalendarFeed()
|
||||
{
|
||||
var start = DateTime.Today.Subtract(TimeSpan.FromDays(7));
|
||||
var end = DateTime.Today.AddDays(28);
|
||||
|
||||
var queryStart = Request.Query.Start;
|
||||
var queryEnd = Request.Query.End;
|
||||
|
||||
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
|
||||
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
|
||||
|
||||
var episodes = _episodeService.EpisodesBetweenDates(start, end);
|
||||
var icalCalendar = new iCalendar();
|
||||
|
||||
foreach (var series in episodes.GroupBy(v => v.Series))
|
||||
{
|
||||
foreach (var episode in series)
|
||||
{
|
||||
var occurrence = icalCalendar.Create<Event>();
|
||||
occurrence.UID = "NzbDrone_episode_" + episode.Id.ToString();
|
||||
occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative;
|
||||
occurrence.Start = new iCalDateTime(episode.AirDateUtc.Value);
|
||||
occurrence.End = new iCalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime));
|
||||
occurrence.Description = episode.Overview;
|
||||
occurrence.Categories = new List<string>() { episode.Series.Network };
|
||||
|
||||
switch (episode.Series.SeriesType)
|
||||
{
|
||||
case SeriesTypes.Daily:
|
||||
occurrence.Summary = string.Format("{0} - {1}", episode.Series.Title, episode.Title);
|
||||
break;
|
||||
|
||||
default:
|
||||
occurrence.Summary = string.Format("{0} - {1}x{2:00} - {3}", episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber, episode.Title);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var serializer = new DDay.iCal.Serialization.iCalendar.SerializerFactory().Build(icalCalendar.GetType(), new DDay.iCal.Serialization.SerializationContext()) as DDay.iCal.Serialization.IStringSerializer;
|
||||
var icalendar = serializer.SerializeToString(icalCalendar);
|
||||
|
||||
return new TextResponse(icalendar, "text/calendar");
|
||||
}
|
||||
}
|
||||
}
|
@ -40,6 +40,9 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="DDay.iCal">
|
||||
<HintPath>..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.SignalR.Core, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.AspNet.SignalR.Core.1.1.3\lib\net40\Microsoft.AspNet.SignalR.Core.dll</HintPath>
|
||||
@ -88,6 +91,7 @@
|
||||
<Compile Include="Blacklist\BlacklistModule.cs" />
|
||||
<Compile Include="Blacklist\BlacklistResource.cs" />
|
||||
<Compile Include="Calendar\CalendarModule.cs" />
|
||||
<Compile Include="Calendar\CalendarFeedModule.cs" />
|
||||
<Compile Include="ClientSchema\SchemaDeserializer.cs" />
|
||||
<Compile Include="ClientSchema\FieldDefinitionAttribute.cs" />
|
||||
<Compile Include="ClientSchema\Field.cs" />
|
||||
@ -139,6 +143,7 @@
|
||||
<Compile Include="Metadata\MetadataResource.cs" />
|
||||
<Compile Include="Metadata\MetadataModule.cs" />
|
||||
<Compile Include="Notifications\NotificationSchemaModule.cs" />
|
||||
<Compile Include="NzbDroneFeedModule.cs" />
|
||||
<Compile Include="ProviderResource.cs" />
|
||||
<Compile Include="ProviderModuleBase.cs" />
|
||||
<Compile Include="Indexers\IndexerSchemaModule.cs" />
|
||||
@ -199,7 +204,9 @@
|
||||
<Compile Include="Validation\RuleBuilderExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="packages.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
|
||||
|
12
src/NzbDrone.Api/NzbDroneFeedModule.cs
Normal file
12
src/NzbDrone.Api/NzbDroneFeedModule.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Nancy;
|
||||
|
||||
namespace NzbDrone.Api
|
||||
{
|
||||
public abstract class NzbDroneFeedModule : NancyModule
|
||||
{
|
||||
protected NzbDroneFeedModule(string resource)
|
||||
: base("/feed/" + resource.Trim('/'))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="DDay.iCal" version="1.0.2.575" targetFramework="net40" />
|
||||
<package id="FluentValidation" version="5.0.0.1" targetFramework="net40" />
|
||||
<package id="Nancy" version="0.21.1" targetFramework="net40" />
|
||||
<package id="Nancy.Authentication.Basic" version="0.21.1" targetFramework="net40" />
|
||||
|
16
src/UI/Calendar/CalendarFeedView.js
Normal file
16
src/UI/Calendar/CalendarFeedView.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
define(
|
||||
[
|
||||
'marionette',
|
||||
], function (Marionette) {
|
||||
return Marionette.Layout.extend({
|
||||
template: 'Calendar/CalendarFeedViewTemplate',
|
||||
|
||||
onRender: function() {
|
||||
// hackish way to determine the correct url, as using urlBase seems to only work for reverse proxies or so
|
||||
var ics = '//' + window.location.host + '/feed/calendar/NzbDrone.ics';
|
||||
this.$('#ical-url').val(window.location.protocol + ics);
|
||||
this.$('#ical-subscribe-button').attr('href', 'webcal:' + ics);
|
||||
}
|
||||
});
|
||||
});
|
26
src/UI/Calendar/CalendarFeedViewTemplate.html
Normal file
26
src/UI/Calendar/CalendarFeedViewTemplate.html
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>NzbDrone Calendar feed</h3>
|
||||
</div>
|
||||
<div class="modal-body edit-series-modal">
|
||||
<div class="row">
|
||||
<div>
|
||||
<div class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ical-url">iCal feed</label>
|
||||
|
||||
<div class="controls">
|
||||
<input type="text" id="ical-url" class="x-ical-url" value="/feed/calendar/NzbDrone.ics" name="ical-url">
|
||||
<span class="help-inline">
|
||||
<i class="icon-nd-form-info" title="copy this url into your clients subscription form or use the subscribe now link if you have a webcal protocol handler installed"/>
|
||||
</span>
|
||||
or <a href="webcal:///feed/calendar/NzbDrone.ics" id="ical-subscribe-button">subscribe now!</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal">close</button>
|
||||
</div>
|
@ -1,10 +1,12 @@
|
||||
'use strict';
|
||||
define(
|
||||
[
|
||||
'AppLayout',
|
||||
'marionette',
|
||||
'Calendar/UpcomingCollectionView',
|
||||
'Calendar/CalendarView'
|
||||
], function (Marionette, UpcomingCollectionView, CalendarView) {
|
||||
'Calendar/CalendarView',
|
||||
'Calendar/CalendarFeedView'
|
||||
], function (AppLayout, Marionette, UpcomingCollectionView, CalendarView, CalendarFeedView) {
|
||||
return Marionette.Layout.extend({
|
||||
template: 'Calendar/CalendarLayoutTemplate',
|
||||
|
||||
@ -13,6 +15,10 @@ define(
|
||||
calendar: '#x-calendar'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click .x-ical': '_showiCal'
|
||||
},
|
||||
|
||||
onShow: function () {
|
||||
this._showUpcoming();
|
||||
this._showCalendar();
|
||||
@ -24,6 +30,11 @@ define(
|
||||
|
||||
_showCalendar: function () {
|
||||
this.calendar.show(new CalendarView());
|
||||
},
|
||||
|
||||
_showiCal: function () {
|
||||
var view = new CalendarFeedView();
|
||||
AppLayout.modalRegion.show(view);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,13 @@
|
||||
<div class="row">
|
||||
<div class="span3">
|
||||
<h4>Upcoming</h4>
|
||||
<div class="pull-left">
|
||||
<h4>Upcoming</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<h4>
|
||||
<i class="icon-calendar-empty ical x-ical"></i>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="x-upcoming"/>
|
||||
</div>
|
||||
<div class=span9>
|
||||
|
@ -158,3 +158,13 @@
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.ical
|
||||
{
|
||||
color: @btnInverseBackground;
|
||||
}
|
||||
|
||||
#ical-url
|
||||
{
|
||||
width: 370px;
|
||||
}
|
@ -23,6 +23,8 @@
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png?v=2"/>
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png?v=2"/>
|
||||
<link rel="icon" type="image/ico" href="/Content/Images/favicon.ico?v=2"/>
|
||||
|
||||
<link rel="alternate" type="text/calendar" title="iCalendar feed for NzbDrone" href="/feed/calendar/NzbDrone.ics" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="nav-region"></div>
|
||||
|
Loading…
Reference in New Issue
Block a user