Skip to content

Commit a4157b3

Browse files
committed
Parsing start time from youtube url to play
1 parent ff19937 commit a4157b3

22 files changed

+137
-76
lines changed

TS3AudioBot/Audio/MetaData.cs

+34-1
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,53 @@
88
// program. If not, see <https://opensource.org/licenses/OSL-3.0>.
99

1010
using System;
11+
using System.Diagnostics.CodeAnalysis;
1112
using TSLib;
1213

1314
namespace TS3AudioBot.Audio
1415
{
1516
public sealed class MetaData
1617
{
1718
/// <summary>Defaults to: invoker.Uid - Can be set if the owner of a song differs from the invoker.</summary>
18-
public Uid ResourceOwnerUid { get; set; }
19+
public Uid? ResourceOwnerUid { get; set; }
1920
/// <summary></summary>
2021
public TimeSpan? StartOffset { get; set; }
2122

2223
public MetaData(TimeSpan? startOffset = null)
2324
{
2425
StartOffset = startOffset;
2526
}
27+
28+
public MetaData Merge(MetaData other) => Merge(this, other);
29+
30+
[return: NotNullIfNotNull("self")]
31+
[return: NotNullIfNotNull("other")]
32+
public static MetaData? Merge(MetaData? self, MetaData? other)
33+
{
34+
if (other is null)
35+
return self;
36+
if (self is null)
37+
return other;
38+
self.ResourceOwnerUid ??= other.ResourceOwnerUid;
39+
self.StartOffset ??= other.StartOffset;
40+
return self;
41+
}
42+
43+
public static MetaData MergeDefault(MetaData? self, MetaData? other)
44+
=> Merge(self, other) ?? new MetaData();
45+
}
46+
47+
public interface IMetaContainer
48+
{
49+
public MetaData? Meta { get; set; }
50+
}
51+
52+
public static class MetaContainerExtensions
53+
{
54+
public static T MergeMeta<T>(this T container, MetaData? other) where T : IMetaContainer
55+
{
56+
container.Meta = MetaData.Merge(container.Meta, other);
57+
return container;
58+
}
2659
}
2760
}

TS3AudioBot/Audio/PlayInfoEventArgs.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public sealed class PlayInfoEventArgs : EventArgs
1616
{
1717
public InvokerData Invoker { get; }
1818
public PlayResource PlayResource { get; }
19-
public AudioResource ResourceData => PlayResource.BaseData;
19+
public AudioResource ResourceData => PlayResource.AudioResource;
2020
public MetaData? MetaData => PlayResource.Meta;
2121
public string? SourceLink { get; }
2222

TS3AudioBot/Audio/PlayManager.cs

+10-12
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public E<LocalStr> Enqueue(InvokerData invoker, string message, string? audioTyp
5858
stats.TrackSongLoad(audioType, false, true);
5959
return result.Error;
6060
}
61-
return Enqueue(invoker, new PlaylistItem(result.Value.BaseData, meta));
61+
return Enqueue(invoker, PlaylistItem.From(result.Value).MergeMeta(meta));
6262
}
6363
public E<LocalStr> Enqueue(InvokerData invoker, IEnumerable<PlaylistItem> items)
6464
{
@@ -104,7 +104,7 @@ public E<LocalStr> Play(InvokerData invoker, AudioResource ar, MetaData? meta =
104104
stats.TrackSongLoad(ar.AudioType, false, true);
105105
return result.Error;
106106
}
107-
return Play(invoker, result.Value, meta);
107+
return Play(invoker, result.Value.MergeMeta(meta));
108108
}
109109

110110
/// <summary>Tries to play the passed link.</summary>
@@ -121,7 +121,7 @@ public E<LocalStr> Play(InvokerData invoker, string link, string? audioType = nu
121121
stats.TrackSongLoad(audioType, false, true);
122122
return result.Error;
123123
}
124-
return Play(invoker, result.Value, meta);
124+
return Play(invoker, result.Value.MergeMeta(meta));
125125
}
126126

127127
public E<LocalStr> Play(InvokerData invoker, IEnumerable<PlaylistItem> items, int index = 0)
@@ -152,14 +152,13 @@ public E<LocalStr> Play(InvokerData invoker, PlaylistItem item)
152152
/// <param name="play">The associated resource type string to a factory.</param>
153153
/// <param name="meta">Allows overriding certain settings for the resource.</param>
154154
/// <returns>Ok if successful, or an error message otherwise.</returns>
155-
public E<LocalStr> Play(InvokerData invoker, PlayResource play, MetaData? meta = null)
155+
public E<LocalStr> Play(InvokerData invoker, PlayResource play)
156156
{
157-
meta ??= new MetaData();
158157
playlistManager.Clear();
159-
playlistManager.Queue(new PlaylistItem(play.BaseData, meta));
158+
playlistManager.Queue(PlaylistItem.From(play));
160159
playlistManager.Index = 0;
161-
stats.TrackSongLoad(play.BaseData.AudioType, true, true);
162-
return StartResource(invoker, play, meta);
160+
stats.TrackSongLoad(play.AudioResource.AudioType, true, true);
161+
return StartResource(invoker, play);
163162
}
164163

165164
private E<LocalStr> StartResource(InvokerData invoker, PlaylistItem item)
@@ -168,13 +167,12 @@ private E<LocalStr> StartResource(InvokerData invoker, PlaylistItem item)
168167
stats.TrackSongLoad(item.AudioResource.AudioType, result.Ok, false);
169168
if (!result)
170169
return result.Error;
171-
return StartResource(invoker, result.Value, item.Meta);
170+
return StartResource(invoker, result.Value.MergeMeta(item.Meta));
172171
}
173172

174-
private E<LocalStr> StartResource(InvokerData invoker, PlayResource play, MetaData? meta = null)
173+
private E<LocalStr> StartResource(InvokerData invoker, PlayResource play)
175174
{
176-
play.Meta = meta ?? play.Meta ?? new MetaData();
177-
var sourceLink = resourceResolver.RestoreLink(play.BaseData).OkOr(null);
175+
var sourceLink = resourceResolver.RestoreLink(play.AudioResource).OkOr(null);
178176
var playInfo = new PlayInfoEventArgs(invoker, play, sourceLink);
179177
BeforeResourceStarted?.Invoke(this, playInfo);
180178

TS3AudioBot/Bot.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,11 @@ public E<string> InitializeBot()
141141
playManager.OnResourceUpdated += LoggedUpdateBotStatus;
142142
// Log our resource in the history
143143
if (Injector.TryGet<HistoryManager>(out var historyManager))
144-
playManager.AfterResourceStarted += (s, e) => { if (e.MetaData != null) historyManager.LogAudioResource(new HistorySaveData(e.PlayResource.BaseData, e.MetaData.ResourceOwnerUid)); };
144+
playManager.AfterResourceStarted += (s, e) =>
145+
{
146+
if (e.MetaData != null)
147+
historyManager.LogAudioResource(new HistorySaveData(e.PlayResource.AudioResource, e.MetaData.ResourceOwnerUid));
148+
};
145149
// Update our thumbnail
146150
playManager.AfterResourceStarted += GenerateStatusImage;
147151
playManager.PlaybackStopped += GenerateStatusImage;
@@ -261,7 +265,7 @@ private void OnMessageReceived(object? sender, TextMessage textMessage)
261265
cachedClientError.Str, infoClientError.Str);
262266
}
263267

264-
var invoker = new ClientCall(textMessage.InvokerUid ?? InvokerData.AnonymousUid, textMessage.Message,
268+
var invoker = new ClientCall(textMessage.InvokerUid ?? Uid.Anonymous, textMessage.Message,
265269
clientId: textMessage.InvokerId,
266270
visibiliy: textMessage.Target,
267271
nickName: textMessage.InvokerName,

TS3AudioBot/History/HistoryManager.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ private R<AudioLogEntry, Exception> CreateLogEntry(HistorySaveData saveData)
163163
nextHid = 0;
164164
}
165165

166-
var ale = new AudioLogEntry(nextHid, saveData.Resource, saveData.InvokerUid.Value)
166+
var userUid = (saveData.InvokerUid ?? Uid.Anonymous).Value ?? Uid.Anonymous.Value!;
167+
var ale = new AudioLogEntry(nextHid, saveData.Resource, userUid)
167168
{
168169
Timestamp = Tools.Now,
169170
PlayCount = 1,

TS3AudioBot/History/HistorySaveData.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ namespace TS3AudioBot.History
1616
public class HistorySaveData
1717
{
1818
public AudioResource Resource { get; }
19-
public Uid InvokerUid { get; }
19+
public Uid? InvokerUid { get; }
2020

21-
public HistorySaveData(AudioResource resource, Uid invokerUid)
21+
public HistorySaveData(AudioResource resource, Uid? invokerUid)
2222
{
2323
Resource = resource ?? throw new ArgumentNullException(nameof(resource));
2424
InvokerUid = invokerUid;

TS3AudioBot/InvokerData.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ namespace TS3AudioBot
1414
public class InvokerData
1515
{
1616
public Uid ClientUid { get; }
17-
public bool IsAnonymous => ClientUid == AnonymousUid;
17+
public bool IsAnonymous => ClientUid == Uid.Anonymous;
1818

19-
public static readonly Uid AnonymousUid = (Uid)"Anonymous";
20-
public static readonly InvokerData Anonymous = new InvokerData(AnonymousUid);
19+
public static readonly InvokerData Anonymous = new InvokerData(Uid.Anonymous);
2120

2221
public InvokerData(Uid clientUid)
2322
{

TS3AudioBot/MainCommands.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public static string CommandApiToken(TokenManager tokenManager, ClientCall invok
106106
{
107107
if (invoker.Visibiliy != null && invoker.Visibiliy != TextMessageTargetMode.Private)
108108
throw new CommandException(strings.error_use_private, CommandExceptionReason.CommandError);
109-
if (invoker.IsAnonymous)
109+
if (invoker.IsAnonymous || invoker.ClientUid == Uid.Null)
110110
throw new MissingContextCommandException(strings.error_no_uid_found, typeof(ClientCall));
111111

112112
TimeSpan? validSpan = null;
@@ -119,7 +119,7 @@ public static string CommandApiToken(TokenManager tokenManager, ClientCall invok
119119
{
120120
throw new CommandException(strings.error_invalid_token_duration, oex, CommandExceptionReason.CommandError);
121121
}
122-
return tokenManager.GenerateToken(invoker.ClientUid.Value, validSpan);
122+
return tokenManager.GenerateToken(invoker.ClientUid.Value!, validSpan);
123123
}
124124

125125
[Command("bot avatar set")]
@@ -395,7 +395,7 @@ public static void CommandFrom(PlayManager playManager, InvokerData invoker, str
395395
public static ushort CommandGetId(ClientCall invoker)
396396
=> invoker.ClientId?.Value ?? throw new CommandException(strings.error_not_found, CommandExceptionReason.CommandError);
397397
[Command("getmy uid")]
398-
public static string CommandGetUid(ClientCall invoker)
398+
public static string? CommandGetUid(ClientCall invoker)
399399
=> invoker.ClientUid.Value;
400400
[Command("getmy name")]
401401
public static string CommandGetName(ClientCall invoker)
@@ -878,7 +878,7 @@ public static JsonValue<PlaylistItemGetData> CommandListAddInternal(ResolveConte
878878
playlistManager.ModifyPlaylist(listId, plist =>
879879
{
880880
var playResource = resourceFactory.Load(link).UnwrapThrow();
881-
var item = new PlaylistItem(playResource.BaseData);
881+
var item = PlaylistItem.From(playResource);
882882
plist.Add(item).UnwrapThrow();
883883
getData = resourceFactory.ToApiFormat(item);
884884
//getData.Index = plist.Items.Count - 1;
@@ -947,7 +947,7 @@ public static JsonValue<PlaylistItemGetData> CommandListAddInternal(PlaylistMana
947947
throw new CommandException(strings.error_playlist_item_index_out_of_range, CommandExceptionReason.CommandError);
948948

949949
var playResource = resourceFactory.Load(link).UnwrapThrow();
950-
var item = new PlaylistItem(playResource.BaseData);
950+
var item = PlaylistItem.From(playResource);
951951
plist.Insert(index, item).UnwrapThrow();
952952
getData = resourceFactory.ToApiFormat(item);
953953
//getData.Index = plist.Items.Count - 1;

TS3AudioBot/Playlists/PlaylistItem.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace TS3AudioBot.Playlists
1616
{
17-
public class PlaylistItem : IAudioResourceResult
17+
public class PlaylistItem : IAudioResourceResult, IMetaContainer
1818
{
1919
public MetaData? Meta { get; set; }
2020
public AudioResource AudioResource { get; }
@@ -25,6 +25,11 @@ public PlaylistItem(AudioResource resource, MetaData? meta = null)
2525
Meta = meta;
2626
}
2727

28+
public static PlaylistItem From(PlayResource playResource)
29+
{
30+
return new PlaylistItem(playResource.AudioResource, playResource.Meta);
31+
}
32+
2833
public override string ToString() => AudioResource.ResourceTitle ?? $"{AudioResource.AudioType}: {AudioResource.ResourceId}";
2934
}
3035
}

TS3AudioBot/ResourceFactories/BandcampResolver.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public R<Stream, LocalStr> GetThumbnail(ResolveContext _, PlayResource playResou
115115
}
116116
if (artId is null)
117117
{
118-
var result = DownloadEmbeddedSite(playResource.BaseData.ResourceId);
118+
var result = DownloadEmbeddedSite(playResource.AudioResource.ResourceId);
119119
if (!result.Ok) return result.Error;
120120
var webSite = result.Value;
121121

Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// TS3AudioBot - An advanced Musicbot for Teamspeak 3
1+
// TS3AudioBot - An advanced Musicbot for Teamspeak 3
22
// Copyright (C) 2017 TS3AudioBot contributors
33
//
44
// This program is free software: you can redistribute it and/or modify
@@ -8,22 +8,23 @@
88
// program. If not, see <https://opensource.org/licenses/OSL-3.0>.
99

1010
using TS3AudioBot.Audio;
11+
using TS3AudioBot.CommandSystem.CommandResults;
1112

1213
namespace TS3AudioBot.ResourceFactories
1314
{
14-
public class PlayResource
15+
public class PlayResource : IAudioResourceResult, IMetaContainer
1516
{
16-
public AudioResource BaseData { get; }
17+
public AudioResource AudioResource { get; }
1718
public string PlayUri { get; }
1819
public MetaData? Meta { get; set; }
1920

2021
public PlayResource(string uri, AudioResource baseData, MetaData? meta = null)
2122
{
22-
BaseData = baseData;
23+
AudioResource = baseData;
2324
PlayUri = uri;
2425
Meta = meta;
2526
}
2627

27-
public override string ToString() => BaseData.ToString();
28+
public override string ToString() => AudioResource.ToString();
2829
}
2930
}

TS3AudioBot/ResourceFactories/ResourceResolver.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,9 @@ public R<string, LocalStr> RestoreLink(ResolveContext ctx, AudioResource res)
191191

192192
public R<Stream, LocalStr> GetThumbnail(ResolveContext ctx, PlayResource playResource)
193193
{
194-
var resolver = GetResolverByType<IThumbnailResolver>(playResource.BaseData.AudioType);
194+
var resolver = GetResolverByType<IThumbnailResolver>(playResource.AudioResource.AudioType);
195195
if (resolver is null)
196-
return new LocalStr(string.Format(strings.error_resfac_no_registered_factory, playResource.BaseData.AudioType));
196+
return new LocalStr(string.Format(strings.error_resfac_no_registered_factory, playResource.AudioResource.AudioType));
197197

198198
var sw = Stopwatch.StartNew();
199199
var result = resolver.GetThumbnail(ctx, playResource);

TS3AudioBot/ResourceFactories/SoundcloudResolver.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public R<Playlist, LocalStr> GetPlaylist(ResolveContext _, string url)
155155

156156
public R<Stream, LocalStr> GetThumbnail(ResolveContext _, PlayResource playResource)
157157
{
158-
if (!WebWrapper.DownloadString($"https://api.soundcloud.com/tracks/{playResource.BaseData.ResourceId}?client_id={SoundcloudClientId}")
158+
if (!WebWrapper.DownloadString($"https://api.soundcloud.com/tracks/{playResource.AudioResource.ResourceId}?client_id={SoundcloudClientId}")
159159
.Get(out var jsonResponse, out var error))
160160
return error;
161161

TS3AudioBot/ResourceFactories/Youtube/YoutubeResolver.cs

+16-3
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
using TS3AudioBot.Playlists;
2020
using TSLib.Helper;
2121
using TS3AudioBot.ResourceFactories.AudioTags;
22+
using TS3AudioBot.Audio;
2223

2324
namespace TS3AudioBot.ResourceFactories.Youtube
2425
{
2526
public sealed class YoutubeResolver : IResourceResolver, IPlaylistResolver, IThumbnailResolver, ISearchResolver
2627
{
2728
private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger();
28-
private static readonly Regex IdMatch = new Regex(@"((&|\?)v=|youtu\.be\/)([\w\-_]{11})", Util.DefaultRegexConfig);
29+
private static readonly Regex IdMatch = new Regex(@"(?:(?:&|\?)v=|youtu\.be\/)([\w\-_]{11})", Util.DefaultRegexConfig);
30+
private static readonly Regex YtTimestampMatch = new Regex(@"(?:&|\?)t=(\d+)", Util.DefaultRegexConfig);
2931
private static readonly Regex LinkMatch = new Regex(@"^(https?\:\/\/)?(www\.|m\.)?(youtube\.|youtu\.be)", Util.DefaultRegexConfig);
3032
private static readonly Regex ListMatch = new Regex(@"(&|\?)list=([\w\-_]+)", Util.DefaultRegexConfig);
3133
private static readonly Regex StreamCodecMatch = new Regex(@"CODECS=""([^""]*)""", Util.DefaultRegexConfig);
@@ -52,7 +54,18 @@ public R<PlayResource, LocalStr> GetResource(ResolveContext? _, string uri)
5254
Match matchYtId = IdMatch.Match(uri);
5355
if (!matchYtId.Success)
5456
return new LocalStr(strings.error_media_failed_to_parse_id);
55-
return GetResourceById(null, new AudioResource(matchYtId.Groups[3].Value, null, ResolverFor));
57+
58+
var result = GetResourceById(null, new AudioResource(matchYtId.Groups[1].Value, null, ResolverFor));
59+
return result.Map(play =>
60+
{
61+
Match matchTimestamp = YtTimestampMatch.Match(uri);
62+
if (matchYtId.Success && int.TryParse(matchTimestamp.Groups[1].Value, out var secs))
63+
{
64+
play.Meta ??= new MetaData();
65+
play.Meta.StartOffset = TimeSpan.FromSeconds(secs);
66+
}
67+
return play;
68+
});
5669
}
5770

5871
public R<PlayResource, LocalStr> GetResourceById(ResolveContext? _, AudioResource resource)
@@ -386,7 +399,7 @@ public R<Stream, LocalStr> GetThumbnail(ResolveContext _, PlayResource playResou
386399
// high : 480px/360px /hqdefault.jpg
387400
// standard : 640px/480px /sddefault.jpg
388401
// maxres : 1280px/720px /maxresdefault.jpg
389-
var imgurl = new Uri($"https://i.ytimg.com/vi/{playResource.BaseData.ResourceId}/mqdefault.jpg");
402+
var imgurl = new Uri($"https://i.ytimg.com/vi/{playResource.AudioResource.ResourceId}/mqdefault.jpg");
390403
return WebWrapper.GetResponseUnsafe(imgurl);
391404
}
392405

TS3AudioBot/TS3AudioBot.csproj

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,17 @@
4242
</PropertyGroup>
4343

4444
<ItemGroup>
45-
<PackageReference Include="GitVersionTask" Version="5.2.3">
45+
<PackageReference Include="GitVersionTask" Version="5.2.4">
4646
<PrivateAssets>all</PrivateAssets>
4747
<IncludeAssets>build</IncludeAssets>
4848
</PackageReference>
49-
<PackageReference Include="NLog" Version="4.6.8" />
49+
<PackageReference Include="NLog" Version="4.7.0" />
5050
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
5151
<PackageReference Include="LiteDB" Version="4.1.4" />
5252
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" />
5353
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
5454
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
55-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0">
55+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.5.0">
5656
<ExcludeAssets>analyzers</ExcludeAssets>
5757
</PackageReference>
5858
<PackageReference Include="Nett" Version="0.15.0" />

TS3AudioBot/Web/Api/ApiCall.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class ApiCall : InvokerData
2020
public Uri? RequestUrl { get; set; }
2121
public string? Body { get; set; }
2222

23-
public static ApiCall CreateAnonymous() => new ApiCall(AnonymousUid);
23+
public static ApiCall CreateAnonymous() => new ApiCall(Uid.Anonymous);
2424

2525
public ApiCall(Uid clientUid, IPAddress? ipAddress = null, Uri? requestUrl = null, string? token = null, string? body = null) : base(clientUid)
2626
{

TSLib/Full/TsFullClient.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ private CmdR DefaultClientInit() => ClientInit(
326326
connectionDataFull.DefaultChannelPassword.HashedPassword,
327327
connectionDataFull.ServerPassword.HashedPassword,
328328
string.Empty, string.Empty, string.Empty,
329-
connectionDataFull.Identity.ClientUid.Value, VersionSign);
329+
connectionDataFull.Identity.ClientUid.ToString(), VersionSign);
330330

331331
/// <summary>
332332
/// Sends a command to the server. Commands look exactly like query commands and mostly also behave identically.

0 commit comments

Comments
 (0)