Browse Source

Initial commit

master
ThePBone 8 months ago
commit
8d14e7054a
42 changed files with 4408 additions and 0 deletions
  1. +89
    -0
      CrunchyrollDownloader.pro
  2. +336
    -0
      CrunchyrollDownloader.pro.user
  3. +1
    -0
      WebLoader
  4. +60
    -0
      collection.cpp
  5. +36
    -0
      collection.h
  6. +74
    -0
      collection.ui
  7. +22
    -0
      colors.h
  8. +510
    -0
      crunchyrollapi.cpp
  9. +64
    -0
      crunchyrollapi.h
  10. +10
    -0
      customcookiejar.cpp
  11. +15
    -0
      customcookiejar.h
  12. +21
    -0
      delegates.h
  13. +100
    -0
      episodes.cpp
  14. +29
    -0
      episodes.h
  15. +118
    -0
      episodes.ui
  16. +27
    -0
      interface/collectionitem.cpp
  17. +25
    -0
      interface/collectionitem.h
  18. +78
    -0
      interface/collectionitem.ui
  19. +70
    -0
      interface/collectionview.cpp
  20. +29
    -0
      interface/collectionview.h
  21. +191
    -0
      interface/collectionview.ui
  22. +71
    -0
      interface/episodeview.cpp
  23. +29
    -0
      interface/episodeview.h
  24. +182
    -0
      interface/episodeview.ui
  25. +251
    -0
      interface/progressview.cpp
  26. +40
    -0
      interface/progressview.h
  27. +221
    -0
      interface/progressview.ui
  28. +33
    -0
      interface/queryitem.cpp
  29. +25
    -0
      interface/queryitem.h
  30. +66
    -0
      interface/queryitem.ui
  31. +63
    -0
      interface/seriesview.cpp
  32. +28
    -0
      interface/seriesview.h
  33. +139
    -0
      interface/seriesview.ui
  34. +195
    -0
      main.cpp
  35. +291
    -0
      mainwindow.cpp
  36. +39
    -0
      mainwindow.h
  37. +434
    -0
      mainwindow.ui
  38. +97
    -0
      models.h
  39. +119
    -0
      prompt.h
  40. +56
    -0
      query.cpp
  41. +33
    -0
      query.h
  42. +91
    -0
      query.ui

+ 89
- 0
CrunchyrollDownloader.pro View File

@@ -0,0 +1,89 @@
#-------------------------------------------------
#
# Project created by QtCreator 2019-11-06T23:28:19
#
#-------------------------------------------------

QT += core gui network xml

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = CrunchyrollDownloader
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

CONFIG += c++11

SOURCES += \
WebLoader/src/HttpMultiPart_p.cpp \
WebLoader/src/NetworkQueue_p.cpp \
WebLoader/src/NetworkRequest.cpp \
WebLoader/src/WebLoader_p.cpp \
WebLoader/src/WebRequest_p.cpp \
collection.cpp \
crunchyrollapi.cpp \
customcookiejar.cpp \
episodes.cpp \
interface/collectionitem.cpp \
interface/collectionview.cpp \
interface/episodeview.cpp \
interface/progressview.cpp \
interface/queryitem.cpp \
interface/seriesview.cpp \
main.cpp \
mainwindow.cpp \
query.cpp

HEADERS += \
WebLoader/src/HttpMultiPart_p.h \
WebLoader/src/NetworkQueue_p.h \
WebLoader/src/NetworkRequest.h \
WebLoader/src/NetworkRequestLoader.h \
WebLoader/src/NetworkRequestPrivate_p.h \
WebLoader/src/WebLoader_p.h \
WebLoader/src/WebRequest_p.h \
collection.h \
colors.h \
crunchyrollapi.h \
customcookiejar.h \
delegates.h \
episodes.h \
interface/collectionitem.h \
interface/collectionview.h \
interface/episodeview.h \
interface/progressview.h \
interface/queryitem.h \
interface/seriesview.h \
mainwindow.h \
models.h \
prompt.h \
query.h

FORMS += \
collection.ui \
episodes.ui \
interface/collectionitem.ui \
interface/collectionview.ui \
interface/episodeview.ui \
interface/progressview.ui \
interface/queryitem.ui \
interface/seriesview.ui \
mainwindow.ui \
query.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

DISTFILES +=

+ 336
- 0
CrunchyrollDownloader.pro.user View File

@@ -0,0 +1,336 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.9.1, 2019-11-09T20:44:21. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{358c80c7-d4c7-4958-b431-97c992ddf723}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.9.8 GCC 64bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.9.8 GCC 64bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.598.gcc_64_kit</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/tim/CrunchyrollDownloader/build-CrunchyrollDownloader-Desktop_Qt_5_9_8_GCC_64bit-Debug</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">qmake</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Debug</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value>
<value type="bool" key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild">true</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/tim/CrunchyrollDownloader/build-CrunchyrollDownloader-Desktop_Qt_5_9_8_GCC_64bit-Release</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">qmake</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">false</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Release</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
<value type="bool" key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild">true</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/tim/CrunchyrollDownloader/build-CrunchyrollDownloader-Desktop_Qt_5_9_8_GCC_64bit-Profile</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">qmake</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">true</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Profile</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Profile</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
<value type="bool" key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild">true</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">3</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy Configuration</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="QString" key="Analyzer.Perf.CallgraphMode">dwarf</value>
<valuelist type="QVariantList" key="Analyzer.Perf.Events">
<value type="QString">cpu-cycles</value>
</valuelist>
<valuelist type="QVariantList" key="Analyzer.Perf.ExtraArguments"/>
<value type="int" key="Analyzer.Perf.Frequency">250</value>
<value type="QString" key="Analyzer.Perf.SampleMode">-F</value>
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Perf.StackSize">4096</value>
<value type="bool" key="Analyzer.QmlProfiler.AggregateTraces">false</value>
<value type="bool" key="Analyzer.QmlProfiler.FlushEnabled">false</value>
<value type="uint" key="Analyzer.QmlProfiler.FlushInterval">1000</value>
<value type="QString" key="Analyzer.QmlProfiler.LastTraceFile"></value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.AddedSuppressionFiles"/>
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectBusEvents">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectSystime">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableBranchSim">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableCacheSim">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableEventToolTips">true</value>
<value type="double" key="Analyzer.Valgrind.Callgrind.MinimumCostRatio">0.01</value>
<value type="double" key="Analyzer.Valgrind.Callgrind.VisualisationMinimumCostRatio">10</value>
<value type="bool" key="Analyzer.Valgrind.FilterExternalIssues">true</value>
<value type="QString" key="Analyzer.Valgrind.KCachegrindExecutable">kcachegrind</value>
<value type="int" key="Analyzer.Valgrind.LeakCheckOnFinish">1</value>
<value type="int" key="Analyzer.Valgrind.NumCallers">25</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.RemovedSuppressionFiles"/>
<value type="int" key="Analyzer.Valgrind.SelfModifyingCodeDetection">1</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.ShowReachable">false</value>
<value type="bool" key="Analyzer.Valgrind.TrackOrigins">true</value>
<value type="QString" key="Analyzer.Valgrind.ValgrindExecutable">valgrind</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.VisibleErrorKinds">
<value type="int">0</value>
<value type="int">1</value>
<value type="int">2</value>
<value type="int">3</value>
<value type="int">4</value>
<value type="int">5</value>
<value type="int">6</value>
<value type="int">7</value>
<value type="int">8</value>
<value type="int">9</value>
<value type="int">10</value>
<value type="int">11</value>
<value type="int">12</value>
<value type="int">13</value>
<value type="int">14</value>
</valuelist>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">CrunchyrollDownloader</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:/home/tim/CrunchyrollDownloader/CrunchyrollDownloader/CrunchyrollDownloader.pro</value>
<value type="QString" key="RunConfiguration.Arguments"></value>
<value type="uint" key="RunConfiguration.QmlDebugServerPort">3768</value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseMultiProcess">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseTerminal">false</value>
<value type="QString" key="RunConfiguration.WorkingDirectory"></value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/tim/CrunchyrollDownloader/build-CrunchyrollDownloader-Desktop_Qt_5_9_8_GCC_64bit-Debug</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">21</value>
</data>
<data>
<variable>Version</variable>
<value type="int">21</value>
</data>
</qtcreator>

+ 1
- 0
WebLoader

@@ -0,0 +1 @@
Subproject commit 52ce73ff0900c2fb7d481996cb6dd219b4560836

+ 60
- 0
collection.cpp View File

@@ -0,0 +1,60 @@
#include "collection.h"
#include "ui_collection.h"
#include <QMessageBox>
#include <QObject>
#include "interface/collectionitem.h"

collection::collection(CrunchyrollAPI* _api,query_t _series,QWidget *parent) :
QDialog(parent),
ui(new Ui::collection)
{
ui->setupUi(this);
api = _api;
series = _series;

connect(ui->results,SIGNAL(itemSelectionChanged()),this,SLOT(selected()));
ui->results->setItemDelegate(new ItemSizeDelegateCollections);
fetchCollections();
}

collection::~collection()
{
delete ui;
}

void collection::fetchCollections(){
ui->results->clear();
auto response = api->getCollections(series.series_id.toInt());
api_status_t status = response.second;
if(status.failed){
QMessageBox::critical(this,"API Error","An error occurred:\n"
+ status.description);
return;
}
QVector<collection_t> results = response.first;
for(collection_t result:results){
collectionitem *item = new collectionitem(result);

QListWidgetItem* li = new QListWidgetItem();
ui->results->addItem(li);
ui->results->setItemWidget(ui->results->item(ui->results->count()-1),item);
}

}

QVector<collection_t> collection::getSelectedCollections(){
return selection;
}

void collection::selected(){
QList<QListWidgetItem*> items = ui->results->selectedItems();
QVector<collection_t> collections;
for(auto item:items){
collectionitem *q = dynamic_cast<collectionitem*>(ui->results->itemWidget(item));
if(q)
collections.push_back(q->getCollection());
else
qWarning("Cannot cast abstract item object");
}
selection = collections;
}

+ 36
- 0
collection.h View File

@@ -0,0 +1,36 @@
#ifndef COLLECTION_H
#define COLLECTION_H

#include <QDialog>
#include <QStyledItemDelegate>
#include <QListWidgetItem>
#include "crunchyrollapi.h"
#include "delegates.h"

namespace Ui {
class collection;
}

class collection : public QDialog
{
Q_OBJECT

public:
explicit collection(CrunchyrollAPI* _api,query_t series, QWidget *parent = nullptr);
~collection();
QVector<collection_t> getSelectedCollections();

private:
Ui::collection *ui;
CrunchyrollAPI* api;
QVector<collection_t> selection;
query_t series;

private slots:
void selected();
void fetchCollections();

};


#endif // COLLECTION_H

+ 74
- 0
collection.ui View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>collection</class>
<widget class="QDialog" name="collection">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>405</width>
<height>271</height>
</rect>
</property>
<property name="windowTitle">
<string>Select collections</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListWidget" name="results">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>collection</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>263</x>
<y>413</y>
</hint>
<hint type="destinationlabel">
<x>263</x>
<y>217</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>collection</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>263</x>
<y>413</y>
</hint>
<hint type="destinationlabel">
<x>263</x>
<y>217</y>
</hint>
</hints>
</connection>
</connections>
</ui>

+ 22
- 0
colors.h View File

@@ -0,0 +1,22 @@
#ifndef _COLORS_H_
#define _COLORS_H_

#define RST "\x1B[0m"
#define KRED "\x1B[91m"
#define KWARN "\x1B[93m"
#define KGREEN "\x1B[92m"
#define KBLUE "\x1B[94m"
#define KHEADER "\x1B[95m"
#define KBOLD "\x1B[1m"

#define FRED(x) KRED x RST
#define FWARN(x) KWARN x RST
#define FBLUE(x) KBLUE x RST
#define FGREEN(x) KGREEN x RST
#define FHEADER(x) KHEADER x RST

#define BOLD(x) "\x1B[1m" x RST
#define UNDL(x) "\x1B[4m" x RST


#endif /* __COLORS_H_ */

+ 510
- 0
crunchyrollapi.cpp View File

@@ -0,0 +1,510 @@
/*
* This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* [email protected] 2019
*/

#include "crunchyrollapi.h"

CrunchyrollAPI::CrunchyrollAPI()
{

}

void CrunchyrollAPI::setLocale(QString _locale){
locale = _locale;
}
QString CrunchyrollAPI::getLocale() const
{
return locale;
}
void CrunchyrollAPI::setSessionID(QString _session){
session = _session;
}
QString CrunchyrollAPI::getSessionID() const
{
return session;
}

QString CrunchyrollAPI::getRandomDeviceID() const
{
const QString possibleCharacters("abcdef0123456789");
const int randomStringLength = 12;

QString randomString;
for(int i=0; i<randomStringLength; ++i)
{
int index = qrand() % possibleCharacters.length();
QChar nextChar = possibleCharacters.at(index);
randomString.append(nextChar);
}
return randomString;
}

QPair<session_t,api_status_t> CrunchyrollAPI::newSession(bool USproxy){
session_t sess;
api_status_t stat;
stat.failed = false;
stat.api_error = false;

qDebug("Requesting session id...");
QString url = "";

if(USproxy)
url = "https://api1.cr-unblocker.com/getsession.php?version=1.1";
else
url = "https://api.crunchyroll.com/start_session.1.json";

NetworkRequest request;
request.setRequestMethod(NetworkRequest::Post);
request.addRequestAttribute("device_id", QVariant(getRandomDeviceID()));
request.addRequestAttribute("device_type", "com.crunchyroll.crunchyroid");
request.addRequestAttribute("access_token", "Scwg9PRRZ19iVwD");

const QByteArray postStatus = request.loadSync(url);
if(request.lastError() != ""){
qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
stat.failed = true;
stat.description = request.lastError();
return QPair<session_t,api_status_t>(sess,stat);
}

QJsonDocument doc = QJsonDocument::fromJson(postStatus);
api_error_t e = checkAPIresponse(doc);
if(e.error){
qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
stat.failed = true;
stat.api_error = true;
stat.description = e.message + " (" + e.code + ")";
return QPair<session_t,api_status_t>(sess,stat);
}

QString country = doc.object().value("data").toObject().value("country_code").toString();
session = doc.object().value("data").toObject().value("session_id").toString();
sess.id = session;
sess.country = country;
qDebug(FBLUE("Session ID received (%s)"),country.toUtf8().constData());
return QPair<session_t,api_status_t>(sess,stat);
}

QPair<user_t,api_status_t> CrunchyrollAPI::doProxyLogin(QString mail,QString pass){
user_t user;
api_status_t stat;
stat.failed = false;
stat.api_error = false;

qDebug("Fetching auth token for proxy login...");
NetworkRequest request_l;
request_l.setRequestMethod(NetworkRequest::Post);
request_l.addRequestAttribute("session_id", QVariant(session).toString());
request_l.addRequestAttribute("account", QVariant(mail).toString());
request_l.addRequestAttribute("password", QVariant(pass).toString());

const QByteArray postStatus_l = request_l.loadSync("https://api.crunchyroll.com/login.2.json");
if(request_l.lastError() != ""){
qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request_l.lastError() << RST;
stat.failed = true;
stat.description = request_l.lastError();
return QPair<user_t,api_status_t>(user,stat);
}

QJsonDocument doc_l = QJsonDocument::fromJson(postStatus_l);
api_error_t e = checkAPIresponse(doc_l);
if(e.error){
qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
stat.failed = true;
stat.api_error = true;
stat.description = e.message + " (" + e.code + ")";
return QPair<user_t,api_status_t>(user,stat);
}

QJsonObject datapart_l = doc_l.object().value("data").toObject();
qDebug(FBLUE("Auth token received"));

qDebug("Logging in via proxy...");
QString url = "https://api2.cr-unblocker.com/start_session?version=1.1&auth="
+ datapart_l.value("auth").toString() + "&user_id=" + datapart_l.value("user").toObject().value("user_id").toString();

NetworkRequest request;
request.setRequestMethod(NetworkRequest::Get);

const QByteArray getStatus = request.loadSync(url);
if(request.lastError() != ""){
qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
stat.failed = true;
stat.description = request.lastError();
return QPair<user_t,api_status_t>(user,stat);
}

QJsonDocument doc = QJsonDocument::fromJson(getStatus);

api_error_t e2 = checkAPIresponse(doc);
if(e2.error){
qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
stat.failed = true;
stat.api_error = true;
stat.description = e2.message + " (" + e2.code + ")";
return QPair<user_t,api_status_t>(user,stat);
}

QJsonObject datapart = doc.object().value("data").toObject();
QString username = datapart.value("user").toObject().value("username").toString();
QString access = datapart.value("user").toObject().value("access_type").toString();
QString expires = datapart.value("expires").toString();

session = datapart.value("session_id").toString();
user.session_id = session;
user.username = username;
user.premium = (access.toLower()=="premium");
qDebug(FBLUE("Logged in as %s (%s)"),username.toUtf8().constData(),access.toUtf8().constData());
return QPair<user_t,api_status_t>(user,stat);
}

QPair<user_t,api_status_t> CrunchyrollAPI::doLogin(QString mail,QString pass){
user_t user;
api_status_t stat;
stat.failed = false;
stat.api_error = false;

qDebug("Logging in...");
NetworkRequest request_l;
request_l.setCookieJar(new CustomCookieJar());
request_l.setRequestMethod(NetworkRequest::Post);
request_l.addRequestAttribute("session_id", QVariant(session).toString());
request_l.addRequestAttribute("account", QVariant(mail).toString());
request_l.addRequestAttribute("password", QVariant(pass).toString());

const QByteArray postStatus_l = request_l.loadSync("https://api.crunchyroll.com/login.2.json");
if(request_l.lastError() != ""){
qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request_l.lastError() << RST;
stat.failed = true;
stat.description = request_l.lastError();
return QPair<user_t,api_status_t>(user,stat);
}

QJsonDocument doc_l = QJsonDocument::fromJson(postStatus_l);
api_error_t e = checkAPIresponse(doc_l);
if(e.error){
qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
stat.failed = true;
stat.api_error = true;
stat.description = e.message + " (" + e.code + ")";
return QPair<user_t,api_status_t>(user,stat);
}

QJsonObject datapart = doc_l.object().value("data").toObject();

QString username = datapart.value("user").toObject().value("username").toString();
QString access = datapart.value("user").toObject().value("access_type").toString();
QString expires = datapart.value("expires").toString();

user.username = username;
user.premium = (access.toLower()=="premium");

qDebug(FBLUE("Logged in as %s (%s)"),username.toUtf8().constData(),access.toUtf8().constData());
CustomCookieJar* cookies = request_l.getCookieJar();

for(auto cookie:cookies->getCookies()){
if(cookie.name()=="session_id"){
session = cookie.value();
user.session_id = session;
return QPair<user_t,api_status_t>(user,stat);
}
}
qWarning(FRED("Login failed. No session id returned"));
stat.failed = true;
stat.description = "No session id returned by the server";
return QPair<user_t,api_status_t>(user,stat);
}

QPair<QVector<query_t>,api_status_t> CrunchyrollAPI::searchMedia(QString query){
api_status_t stat;
stat.failed = false;
stat.api_error = false;
QVector<query_t> response;

qDebug("Searching for '%s'",query.toUtf8().constData());
NetworkRequest request;
request.setRequestMethod(NetworkRequest::Post);
request.addRequestAttribute("session_id", session);
request.addRequestAttribute("q", query);
request.addRequestAttribute("limit", "50");
request.addRequestAttribute("offset", "0");
request.addRequestAttribute("media_types", "anime|drama");

const QByteArray postStatus = request.loadSync("https://api.crunchyroll.com/autocomplete.0.json");
if(request.lastError() != ""){
qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
stat.failed = true;
stat.description = request.lastError();
return QPair<QVector<query_t>,api_status_t>(response,stat);
}

QJsonDocument doc = QJsonDocument::fromJson(postStatus);
api_error_t e = checkAPIresponse(doc);
if(e.error){
qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
stat.failed = true;
stat.api_error = true;
stat.description = e.message + " (" + e.code + ")";
return QPair<QVector<query_t>,api_status_t>(response,stat);
}

QJsonArray results = doc.object().value("data").toArray();

for (auto result:results) {
query_t query;
QJsonObject obj = result.toObject();
query.name = obj.value("name").toString();
query.series_id = obj.value("series_id").toString();
query.thumbnail = QUrl(obj.value("portrait_image").toObject().value("thumb_url").toString());
query.description = obj.value("description").toString();
response.push_back(query);
}
if(response.count()==0)
qDebug(FRED("No results"));
else if(response.count()==1)
qDebug(FBLUE("1 result found (%s)"),response.first().name.toUtf8().constData());
else
qDebug(FBLUE("%d results found"), response.count());

return QPair<QVector<query_t>,api_status_t>(response,stat);
}

QPair<QVector<collection_t>,api_status_t> CrunchyrollAPI::getCollections(int seriesid){
api_status_t stat;
stat.failed = false;
stat.api_error = false;
QVector<collection_t> response;

QString fields = "collections.collection_id,collections.series_id,collections.name,\
collections.series_name,collections.description,collections.media_type,\
collections.season,collections.complete,collections.landscape_image,\
collections.portrait_image,collections.availability_notes,collections.media_count,\
collections.premium_only,collections.created,collections.mature";

NetworkRequest request;
request.setRequestMethod(NetworkRequest::Post);
request.addRequestAttribute("session_id", QVariant(session).toString());
request.addRequestAttribute("series_id", QVariant(seriesid).toString());
request.addRequestAttribute("sort", "asc");
request.addRequestAttribute("locale", QVariant(locale).toString());
request.addRequestAttribute("fields", fields);

const QByteArray postStatus = request.loadSync("https://api.crunchyroll.com/list_collections.0.json");
if(request.lastError() != ""){
qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
stat.failed = true;
stat.description = request.lastError();
return QPair<QVector<collection_t>,api_status_t>(response,stat);
}

QJsonDocument doc = QJsonDocument::fromJson(postStatus);
api_error_t e = checkAPIresponse(doc);
if(e.error){
qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
stat.failed = true;
stat.api_error = true;
stat.description = e.message + " (" + e.code + ")";
return QPair<QVector<collection_t>,api_status_t>(response,stat);
}

QJsonArray results = doc.object().value("data").toArray();

for (auto result:results) {
collection_t c;
QJsonObject obj = result.toObject();
c.name = obj.value("name").toString();
c.collection_id = obj.value("collection_id").toString();
c.season = obj.value("season").toString();
c.description = obj.value("description").toString();
response.push_back(c);
}
if(response.count()==0)
qDebug(FRED("No collections"));
else
qDebug(FBLUE("%d collection(s) found"), response.count());

return QPair<QVector<collection_t>,api_status_t>(response,stat);
}

QPair<QVector<meta_episode_t>,api_status_t> CrunchyrollAPI::getEpisodes(int SeriesOrCollectionID, bool useSeriesID){
QVector<meta_episode_t> response;
api_status_t stat;
stat.failed = false;
stat.api_error = false;

NetworkRequest request;
request.setRequestMethod(NetworkRequest::Post);
request.addRequestAttribute("session_id", QVariant(session).toString());

if(useSeriesID)request.addRequestAttribute("series_id", QVariant(SeriesOrCollectionID));
else request.addRequestAttribute("collection_id", QVariant(SeriesOrCollectionID));

request.addRequestAttribute("sort", "asc");
request.addRequestAttribute("locale", QVariant(locale).toString());
request.addRequestAttribute("offset", "0");
request.addRequestAttribute("limit", "5000");

const QByteArray postStatus = request.loadSync("https://api.crunchyroll.com/list_media.0.json");
if(request.lastError() != ""){
qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
stat.failed = true;
stat.description = request.lastError();
return QPair<QVector<meta_episode_t>,api_status_t>(response,stat);
}

QJsonDocument doc = QJsonDocument::fromJson(postStatus);
api_error_t e = checkAPIresponse(doc);
if(e.error){
qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
stat.failed = true;
stat.api_error = true;
stat.description = e.message + " (" + e.code + ")";
return QPair<QVector<meta_episode_t>,api_status_t>(response,stat);
}

QJsonArray results = doc.object().value("data").toArray();

for (auto result:results) {
meta_episode_t c;
QJsonObject obj = result.toObject();
c.name = obj.value("name").toString();
c.number = obj.value("episode_number").toString().toInt();
c.media_id = obj.value("media_id").toString();
c.description = obj.value("description").toString();
c.available = obj.value("available").toBool();
c.free_available = obj.value("free_available").toBool();
c.thumbnail = QUrl(obj.value("screenshot_image").toObject().value("thumb_url").toString());
response.push_back(c);
}
if(response.count()==0)
qDebug(FRED("No episodes found"));
else
qDebug(FBLUE("%d episode(s) found"), response.count());

return QPair<QVector<meta_episode_t>,api_status_t>(response,stat);
}

QPair<episode_t,api_status_t> CrunchyrollAPI::getEpisode(int mediaID){
api_status_t stat;
stat.failed = false;
stat.api_error = false;
episode_t c;

NetworkRequest request;
request.setRequestMethod(NetworkRequest::Post);
request.addRequestAttribute("session_id", QVariant(session).toString());
request.addRequestAttribute("media_id", QVariant(mediaID).toString());
request.addRequestAttribute("locale", QVariant(locale).toString());
request.addRequestAttribute("fields", "media.collection_id,media.collection_name,\
media.stream_data,media.available,media.episode_number,media.duration,\
media.series_name,media.premium_only,media.name,media.screenshot_image");

const QByteArray postStatus = request.loadSync("https://api.crunchyroll.com/info.0.json");
if(request.lastError() != ""){
qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
stat.failed = true;
stat.description = request.lastError();
return QPair<episode_t,api_status_t>(c,stat);
}

QJsonDocument doc = QJsonDocument::fromJson(postStatus);
api_error_t e = checkAPIresponse(doc);
if(e.error){
qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
stat.failed = true;
stat.api_error = true;
stat.description = e.message + " (" + e.code + ")";
return QPair<episode_t,api_status_t>(c,stat);
}

QJsonObject obj = doc.object().value("data").toObject();

c.name = obj.value("name").toString();
c.number = obj.value("episode_number").toString().toInt();
c.seriesname = obj.value("series_name").toString();
c.description = obj.value("description").toString();
c.collectionname = obj.value("collection_name").toString();
c.audio_lang = obj.value("audio_lang").toString();
c.subtitle_lang = obj.value("hardsub_lang").toString();
c.available = obj.value("available").toBool();
c.duration = obj.value("duration").toInt();
c.premium_only = obj.value("premium_only").toBool();
c.thumbnail = QUrl(obj.value("screenshot_image").toObject().value("thumb_url").toString());

QVector<stream_t> stream_container;
QJsonArray streams = obj.value("stream_data").toObject().value("streams").toArray();
for(auto stream:streams){
stream_t st;
QJsonObject s = stream.toObject();
st.quality = s.value("quality").toString();
st.url = QUrl(s.value("url").toString());
stream_container.push_back(st);
}
c.streams = stream_container;
return QPair<episode_t,api_status_t>(c,stat);
}

QPair<QString,api_status_t> CrunchyrollAPI::downloadM3U(episode_t episode){
api_status_t stat;
stat.failed = false;
stat.api_error = false;
for(stream_t stream:episode.streams){
QString quality = stream.quality;
if(quality.toLower() == "adaptive"){
NetworkRequest request;
request.setRequestMethod(NetworkRequest::Get);
const QByteArray postStatus = request.loadSync(stream.url);
if(request.lastError() != ""){
qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
stat.failed = true;
stat.description = request.lastError();
return QPair<QString,api_status_t>("",stat);
}

return QPair<QString,api_status_t>(QString::fromUtf8(postStatus),stat);
}
else continue;
}

qDebug(FRED("No adaptive stream available"));
return QPair<QString,api_status_t>("",stat);
}

void CrunchyrollAPI::printEpisodeDetails(episode_t episode){
std::cout << KBOLD << "#" << episode.number << " - " << episode.name.toUtf8().constData()
<< RST << " (" << episode.collectionname.toUtf8().constData() << ")" << std::endl;
if(episode.available)
std::cout << "Available: " << KGREEN << "true " << RST;
else
std::cout << "Available: " << KRED << "false " << RST;
if(episode.premium_only)
std::cout << "Premium only: " << KRED << "true" << RST << std::endl;
else
std::cout << "Premium only: " << KGREEN << "false" << RST << std::endl;
if(episode.streams.count() < 1)
std::cout << KBOLD << KRED << "Either this episode is premium or not available in the selected locale ("
<< locale.toUtf8().constData() << ")" << RST << std::endl;
}

api_error_t CrunchyrollAPI::checkAPIresponse(QJsonDocument doc){
QJsonObject obj = doc.object();
api_error_t e;
e.error = obj.value("error").toBool();
e.code = obj.value("code").toString();
if(obj.keys().contains("message")) e.message = obj.value("message").toString();
else e.message = "";
return e;
}

+ 64
- 0
crunchyrollapi.h View File

@@ -0,0 +1,64 @@
/*
* This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* [email protected] 2019
*/

#ifndef CRUNCHYROLLAPI_H
#define CRUNCHYROLLAPI_H
#include <QVariant>
#include <QFile>
#include <QDir>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QNetworkCookieJar>
#include <QNetworkCookie>
#include <QPair>
#include <iostream>

#include "customcookiejar.h"
#include "models.h"
#include "colors.h"
#include "WebLoader/src/NetworkRequestLoader.h"

class CrunchyrollAPI
{
public:
CrunchyrollAPI();
QString getRandomDeviceID() const;
//Configuration
void setLocale(QString locale);
QString getLocale() const;
void setSessionID(QString session);
QString getSessionID() const;
//API
QPair<session_t,api_status_t> newSession(bool USproxy);
QPair<user_t,api_status_t> doLogin(QString mail,QString pass);
QPair<user_t,api_status_t> doProxyLogin(QString mail,QString pass);
QPair<QVector<query_t>,api_status_t> searchMedia(QString query);
QPair<QVector<collection_t>,api_status_t> getCollections(int seriesid);
QPair<QVector<meta_episode_t>,api_status_t> getEpisodes(int SeriesOrCollectionID, bool useSeriesID);
QPair<episode_t,api_status_t> getEpisode(int mediaID);
QPair<QString,api_status_t> downloadM3U(episode_t episode);
void printEpisodeDetails(episode_t episode);

private:
QString session;
QString locale = "enUS";
api_error_t checkAPIresponse(QJsonDocument doc);
};

#endif // CRUNCHYROLLAPI_H

+ 10
- 0
customcookiejar.cpp View File

@@ -0,0 +1,10 @@
#include "customcookiejar.h"

CustomCookieJar::CustomCookieJar()
{

}
QList<QNetworkCookie> CustomCookieJar::getCookies() const
{
return this->allCookies();
}

+ 15
- 0
customcookiejar.h View File

@@ -0,0 +1,15 @@
#ifndef CUSTOMCOOKIEJAR_H
#define CUSTOMCOOKIEJAR_H

#include <QObject>
#include <QNetworkCookieJar>
#include <QNetworkCookie>

class CustomCookieJar : public QNetworkCookieJar
{
public:
CustomCookieJar();
QList<QNetworkCookie> getCookies() const;
};

#endif // CUSTOMCOOKIEJAR_H

+ 21
- 0
delegates.h View File

@@ -0,0 +1,21 @@
#ifndef DELEGATES_H
#define DELEGATES_H
#include <QStyledItemDelegate>

class ItemSizeDelegate : public QStyledItemDelegate {
public:
ItemSizeDelegate(QObject *parent=0) : QStyledItemDelegate (parent){}

QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const{
return QSize(option.widget->height()+5, 80);
}
};
class ItemSizeDelegateCollections : public QStyledItemDelegate {
public:
ItemSizeDelegateCollections(QObject *parent=0) : QStyledItemDelegate (parent){}

QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const{
return QSize(option.widget->height()+5, 47);
}
};
#endif // DELEGATES_H

+ 100
- 0
episodes.cpp View File

@@ -0,0 +1,100 @@
#include "episodes.h"
#include "ui_episodes.h"

episodes::episodes(CrunchyrollAPI* _api,QVector<meta_episode_t> _allEpisodes,
QVector<meta_episode_t> currentEpisodes,QWidget *parent) :
QDialog(parent),
ui(new Ui::episodes)
{
ui->setupUi(this);
api = _api;
allEpisodes = _allEpisodes;

for(auto e:allEpisodes){
ui->e_table->insertRow(ui->e_table->rowCount());

collection_t c = e.collection;
for(int i = 0; i < 7;i++){
QTableWidgetItem* item = new QTableWidgetItem();
switch (i) {
case 0:
item->setText(c.name);
break;
case 1:
item->setText("Season " + c.season);
break;
case 2:
item->setText("E"+QString::number(e.number));
break;
case 3:
item->setText(e.name);
break;
case 4:
item->setText(e.free_available ? "true" : "false");
break;
case 5:
item->setText(e.available ? "true" : "false");
break;
case 6:
item->setText(e.media_id);
break;
}
ui->e_table->setItem(ui->e_table->rowCount()-1,i,item);
}
}

QVector<int> rows;
for(auto cur:currentEpisodes){
for(int i = 0; i < ui->e_table->rowCount(); i++){
if(ui->e_table->item(i,6)->text()==cur.media_id){
for (int j = 0; j < 7; j++) {
ui->e_table->selectionModel()->select(
ui->e_table->model()->index(i,j),
QItemSelectionModel::SelectionFlag::Select);
}
break;
}
}
}
ui->e_table->setFocus();

ui->e_table->horizontalHeader()->resizeSection(0,200);
ui->e_table->horizontalHeader()->resizeSection(1,75);
ui->e_table->horizontalHeader()->resizeSection(2,50);
ui->e_table->horizontalHeader()->resizeSection(3,200);
ui->e_table->horizontalHeader()->resizeSection(4,50);
ui->e_table->horizontalHeader()->resizeSection(5,65);
ui->e_table->horizontalHeader()->resizeSection(6,0);
connect(ui->e_table,SIGNAL(itemSelectionChanged()),this,SLOT(selected()));
}

episodes::~episodes()
{
delete ui;
}

QVector<meta_episode_t> episodes::getSelectedEpisodes(){
return selection;
}

void episodes::selected(){
QVector<int> rows;
auto selectedItems = ui->e_table->selectedItems();
for(auto item:selectedItems){
if(!rows.contains(item->row()))
rows.append(item->row());
}

QVector<meta_episode_t> sel;
for(int row:rows){
QString id = ui->e_table->item(row,6)->text();
//Find episode by media id
for(auto ae:allEpisodes){
if(ae.media_id==id){
sel.push_back(ae);
break;
}
}
}
selection = sel;
}

+ 29
- 0
episodes.h View File

@@ -0,0 +1,29 @@
#ifndef EPISODES_H
#define EPISODES_H

#include <QDialog>
#include "crunchyrollapi.h"

namespace Ui {
class episodes;
}

class episodes : public QDialog
{
Q_OBJECT

public:
explicit episodes(CrunchyrollAPI* _api,QVector<meta_episode_t> _allEpisodes,
QVector<meta_episode_t> currentEpisodes,QWidget *parent = nullptr);
~episodes();
QVector<meta_episode_t> getSelectedEpisodes();
private slots:
void selected();
private:
Ui::episodes *ui;
CrunchyrollAPI* api;
QVector<meta_episode_t> selection;
QVector<meta_episode_t> allEpisodes;
};

#endif // EPISODES_H

+ 118
- 0
episodes.ui View File

@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>episodes</class>
<widget class="QDialog" name="episodes">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>769</width>
<height>356</height>
</rect>
</property>
<property name="windowTitle">
<string>Select episodes</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableWidget" name="e_table">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string>Collection</string>
</property>
</column>
<column>
<property name="text">
<string>Season</string>
</property>
</column>
<column>
<property name="text">
<string>Ep.</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Free</string>
</property>
</column>
<column>
<property name="text">
<string>Available</string>
</property>
</column>
<column>
<property name="text">
<string>ID</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>episodes</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>278</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>episodes</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>278</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
</ui>

+ 27
- 0
interface/collectionitem.cpp View File

@@ -0,0 +1,27 @@
#include "collectionitem.h"
#include "ui_collectionitem.h"
#include "WebLoader/src/NetworkRequestLoader.h"

collectionitem::collectionitem(collection_t q,QWidget *parent) :
QWidget(parent),
ui(new Ui::collectionitem)
{
ui->setupUi(this);
setCollection(q);
}

collectionitem::~collectionitem()
{
delete ui;
}

void collectionitem::setCollection(collection_t q){
c_series = q;

ui->c_title->setText(q.name);
ui->c_season->setText("Season " + q.season);
}

collection_t collectionitem::getCollection(){
return c_series;
}

+ 25
- 0
interface/collectionitem.h View File

@@ -0,0 +1,25 @@
#ifndef COLLECTIONITEM_H
#define COLLECTIONITEM_H

#include <QWidget>
#include "models.h"

namespace Ui {
class collectionitem;
}

class collectionitem : public QWidget
{
Q_OBJECT

public:
explicit collectionitem(collection_t q,QWidget *parent = nullptr);
~collectionitem();
void setCollection(collection_t q);
collection_t getCollection();
private:
Ui::collectionitem *ui;
collection_t c_series;
};

#endif // COLLECTIONITEM_H

+ 78
- 0
interface/collectionitem.ui View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>collectionitem</class>
<widget class="QWidget" name="collectionitem">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>308</width>
<height>47</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>47</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>1</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<item>
<widget class="QLabel" name="c_title">
<property name="styleSheet">
<string notr="true">*{
font-weight: 600;
}</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&amp;lt;Unknown collection&amp;gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="c_season">
<property name="text">
<string>Season 0</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

+ 70
- 0
interface/collectionview.cpp View File

@@ -0,0 +1,70 @@
#include "collectionview.h"
#include "ui_collectionview.h"
#include "WebLoader/src/NetworkRequestLoader.h"
#include <QPainter>

CollectionView::CollectionView(QWidget *parent) :
QFrame(parent),
ui(new Ui::CollectionView)
{
ui->setupUi(this);
connect(ui->change,SIGNAL(clicked()),this,SIGNAL(changeButtonClicked()));
}

CollectionView::~CollectionView()
{
delete ui;
}

void CollectionView::setContent(QVector<collection_t> c){
if(c.count()>0){
QString strbuilder = "";
int i = 0;
for(collection_t sc:c){
strbuilder += sc.name;
if(!(i >= c.count()-1))
strbuilder += ",\n";
i++;
}
ui->c_title->setText(QString::number(c.count())+" collection(s) selected");
ui->c_desc->setText(strbuilder);
ui->c_count->setText(QString::number(c.count()));
}else{
ui->c_count->setText("");
ui->c_title->setText("<No collections selected>");
ui->c_desc->setText("Either the selected series is unavailable in your region or an api error has occurred");
}
}

void CollectionView::viewScanText(){
ui->c_count->setText("...");
ui->c_title->setText("Searching for collections...");
ui->c_desc->setText("This could take a short while");
}

void CollectionView::paintEvent(QPaintEvent*){
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);

QRectF r = rect();
r = r.marginsAdded(QMarginsF(-1.5, -1.5, -1.5, -0.5));

QColor color;
color = QColor(200, 200, 200);
painter.setPen(color);

QLinearGradient gradient(r.topLeft(), r.bottomLeft());
gradient.setColorAt(0, QColor(255, 255, 255));
gradient.setColorAt(1, QColor(230, 230, 230));
painter.setBrush(gradient);
painter.drawRoundedRect(r, 5, 5);

QRectF r2 = r;
r2.setRight(ui->label->geometry().right() - 0.5);

QLinearGradient gradient2(r2.topLeft(), r2.bottomLeft());
gradient2.setColorAt(0, color.darker(110));
gradient2.setColorAt(1, color.darker(200));
painter.setBrush(gradient2);
painter.drawRoundedRect(r2, 5, 5);
}

+ 29
- 0
interface/collectionview.h View File

@@ -0,0 +1,29 @@
#ifndef COLLECTIONVIEW_H
#define COLLECTIONVIEW_H

#include <QFrame>
#include "models.h"

namespace Ui {
class CollectionView;
}

class CollectionView : public QFrame
{
Q_OBJECT

public:
explicit CollectionView(QWidget *parent = nullptr);
~CollectionView();
void setContent(QVector<collection_t> c);
void viewScanText();

signals:
void changeButtonClicked();

private:
Ui::CollectionView *ui;
void paintEvent(QPaintEvent*);
};

#endif // COLLECTIONVIEW_H

+ 191
- 0
interface/collectionview.ui View File

@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CollectionView</class>
<widget class="QFrame" name="CollectionView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>412</width>
<height>119</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>119</height>
</size>
</property>
<property name="windowTitle">
<string>Frame</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>14</width>
<height>0</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">*{
color: rgb(255, 255, 255);
}</string>
</property>
<property name="text">
<string>2</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="c_count">
<property name="minimumSize">
<size>
<width>48</width>
<height>76</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>76</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">*{
font-size: 15pt;
}</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="c_title">
<property name="styleSheet">
<string notr="true">*{
font-weight: 600;
}</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&amp;lt;No collections selected&amp;gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="minimumSize">
<size>
<width>0</width>
<height>75</height>
</size>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>226</width>
<height>74</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="c_desc">
<property name="text">
<string>Click on the button on the right to select one or more collections/seasons</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="rightMargin">
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="change">
<property name="text">
<string>Change</string>
</property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

+ 71
- 0
interface/episodeview.cpp View File

@@ -0,0 +1,71 @@
#include "episodeview.h"
#include "ui_episodeview.h"
#include "WebLoader/src/NetworkRequestLoader.h"
#include <QPainter>

EpisodeView::EpisodeView(QWidget *parent) :
QFrame(parent),
ui(new Ui::EpisodeView)
{
ui->setupUi(this);
connect(ui->change,SIGNAL(clicked()),this,SIGNAL(changeButtonClicked()));
}

EpisodeView::~EpisodeView()
{
delete ui;
}

void EpisodeView::setEpisodes(QVector<meta_episode_t> e){
if(e.count()>0){
QString strbuilder = "";
int i = 0;
for(meta_episode_t sc:e){
strbuilder += QString::number(sc.number);
strbuilder += ". ";
strbuilder += sc.name;
if(!(i >= e.count()-1))
strbuilder += ",\n";
i++;
}
ui->e_title->setText(QString::number(e.count())+" episode(s) selected");
ui->e_desc->setText(strbuilder);
ui->e_count->setText(QString::number(e.count()));
}else{
ui->e_count->setText("");
ui->e_title->setText("<No episodes selected>");
ui->e_desc->setText("Click on the button on the right to select episodes");
}
}
void EpisodeView::viewScanText(){
ui->e_count->setText("...");
ui->e_title->setText("Searching for episodes...");
ui->e_desc->setText("This could take a short while");
}

void EpisodeView::paintEvent(QPaintEvent*){
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);

QRectF r = rect();
r = r.marginsAdded(QMarginsF(-1.5, -1.5, -1.5, -0.5));

QColor color;
color = QColor(200, 200, 200);
painter.setPen(color);

QLinearGradient gradient(r.topLeft(), r.bottomLeft());
gradient.setColorAt(0, QColor(255, 255, 255));
gradient.setColorAt(1, QColor(230, 230, 230));
painter.setBrush(gradient);
painter.drawRoundedRect(r, 5, 5);

QRectF r2 = r;
r2.setRight(ui->label->geometry().right() - 0.5);

QLinearGradient gradient2(r2.topLeft(), r2.bottomLeft());
gradient2.setColorAt(0, color.darker(110));
gradient2.setColorAt(1, color.darker(200));
painter.setBrush(gradient2);
painter.drawRoundedRect(r2, 5, 5);
}

+ 29
- 0
interface/episodeview.h View File

@@ -0,0 +1,29 @@
#ifndef EPISODEVIEW_H
#define EPISODEVIEW_H

#include <QFrame>
#include "models.h"

namespace Ui {
class EpisodeView;
}

class EpisodeView : public QFrame
{
Q_OBJECT

public:
explicit EpisodeView(QWidget *parent = nullptr);
~EpisodeView();
void setEpisodes(QVector<meta_episode_t> e);
void viewScanText();

signals:
void changeButtonClicked();

private:
Ui::EpisodeView *ui;
void paintEvent(QPaintEvent*);
};

#endif // EPISODEVIEW_H

+ 182
- 0
interface/episodeview.ui View File

@@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EpisodeView</class>
<widget class="QFrame" name="EpisodeView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>428</width>
<height>212</height>
</rect>
</property>
<property name="windowTitle">
<string>Frame</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,20,0">
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>15</width>
<height>0</height>
<