├── src ├── OpenPr0gramm │ ├── ItemStatus.cs │ ├── Generic │ │ └── Response │ │ │ ├── IHasEndResponse.cs │ │ │ └── IMessageResponse.cs │ ├── Vote.cs │ ├── Model │ │ ├── SyncLogItem.cs │ │ └── SyncAction.cs │ ├── Response │ │ ├── GetCheckoutUrlResponse.cs │ │ ├── TokenInfoResponse.cs │ │ ├── GetPaymentAddressResponse.cs │ │ ├── LogInResponse.cs │ │ ├── TokenResponse.cs │ │ ├── LoadInviteResponse.cs │ │ ├── ChangeEmailResponse.cs │ │ ├── GetDetailsResponse.cs │ │ ├── GetFollowListResponse.cs │ │ ├── LoggedInResponse.cs │ │ ├── GetItemsInfoResponse.cs │ │ ├── GetMessagesResponse.cs │ │ ├── SuccessableResponse.cs │ │ ├── Pr0grammResponse.cs │ │ ├── GetItemsResponse.cs │ │ ├── GetUserInfoResponse.cs │ │ ├── CaptchaResponse.cs │ │ ├── GetCommentsResponse.cs │ │ ├── SyncResponse.cs │ │ └── GetProfileInfoResponse.cs │ ├── Endpoint │ │ ├── User │ │ │ └── Response │ │ │ │ └── GetUserNameResponse.cs │ │ └── Inbox │ │ │ ├── Model │ │ │ ├── InboxItemType.cs │ │ │ ├── InboxConversation.cs │ │ │ ├── InboxPrivateMessage.cs │ │ │ └── InboxItem.cs │ │ │ ├── Response │ │ │ ├── GetInboxCommentResponse.cs │ │ │ ├── GetInboxOverviewResponse.cs │ │ │ ├── GetInboxNotificationResponse.cs │ │ │ ├── GetInboxConversationResponse.cs │ │ │ └── GetInboxPrivateMessageResponse.cs │ │ │ ├── FormData │ │ │ └── PrivateMessageData.cs │ │ │ └── IPr0grammInboxService.cs │ ├── ItemTagVote.cs │ ├── Pr0miumProducts.cs │ ├── ItemFlags.cs │ ├── Service │ │ ├── IPr0grammContactService.cs │ │ ├── IPr0grammPaypalService.cs │ │ ├── IPr0grammBitcoinService.cs │ │ ├── IPr0grammTagsService.cs │ │ ├── IPr0grammProfileService.cs │ │ ├── IPr0grammCommentsService.cs │ │ ├── IPr0grammItemsService.cs │ │ └── IPr0grammUserService.cs │ ├── Token.cs │ ├── FormData │ │ ├── FollowData.cs │ │ ├── InviteData.cs │ │ ├── SendPasswordResetMailData.cs │ │ ├── TokenActionData.cs │ │ ├── VoteData.cs │ │ ├── ChangeEmailData.cs │ │ ├── DeleteCommentData.cs │ │ ├── EditCommentData.cs │ │ ├── LogOutData.cs │ │ ├── RequestEmailChangeData.cs │ │ ├── ChangeAccountSettingData.cs │ │ ├── PostFormData.cs │ │ ├── ChangePasswordData.cs │ │ ├── PaymentData.cs │ │ ├── BanData.cs │ │ ├── AddTagsData.cs │ │ ├── SiteSettingsData.cs │ │ ├── LogInData.cs │ │ ├── PostCommentData.cs │ │ ├── ContactData.cs │ │ ├── ResetPasswordData.cs │ │ ├── DeleteTagsData.cs │ │ ├── DeleteItemData.cs │ │ └── JoinWithTokenData.cs │ ├── LikedItem.cs │ ├── BanInfo.cs │ ├── UserMark.cs │ ├── ProfileUpload.cs │ ├── AccountInfo.cs │ ├── OpenPr0gramm.csproj │ ├── Json │ │ ├── ImageUrlConverter.cs │ │ ├── ThumbUrlConverter.cs │ │ ├── UnixDateTimeConverter.cs │ │ └── SyncLogItemConverter.cs │ ├── Tag.cs │ ├── DateTimeEx.cs │ ├── LoggingMessageHandler.cs │ ├── EnumsAsIntegersParameterFormatter.cs │ ├── ClientConstants.cs │ ├── JsonNet.PrivateSettersContractResolvers │ │ └── PrivateSettersContractResolvers.cs │ ├── Comment.cs │ ├── Item.cs │ ├── User.cs │ ├── ItemDownloader.cs │ ├── ProfileBadge.cs │ ├── Pr0grammApiClient.cs │ └── Pr0grammClient.cs ├── OpenPr0gramm.Tests │ ├── OpenPr0gramm.Tests.csproj │ └── ItemDownloaderTests.cs └── Common.props ├── scripts └── deploy.sh ├── .editorconfig ├── CONTRIBUTING.md ├── .github └── workflows │ ├── CD.yml │ └── CI.yml ├── README.md ├── OpenPr0gramm.sln ├── .gitignore └── LICENSE /src/OpenPr0gramm/ItemStatus.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace OpenPr0gramm 3 | { 4 | public enum ItemStatus 5 | { 6 | New = 0, 7 | Promoted = 1 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Generic/Response/IHasEndResponse.cs: -------------------------------------------------------------------------------- 1 | namespace OpenPr0gramm.Generic.Response 2 | { 3 | public interface IHasEndResponse 4 | { 5 | bool AtEnd { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Vote.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace OpenPr0gramm 3 | { 4 | public enum Vote 5 | { 6 | Down = -1, 7 | Neutral = 0, 8 | Up = 1, 9 | Favourite = 2 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Model/SyncLogItem.cs: -------------------------------------------------------------------------------- 1 | namespace OpenPr0gramm.Model 2 | { 3 | public class SyncLogItem 4 | { 5 | public int Id { get; set; } 6 | public SyncAction Action { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetCheckoutUrlResponse.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace OpenPr0gramm 3 | { 4 | public class GetCheckoutUrlResponse : Pr0grammResponse 5 | { 6 | public string CheckoutUrl { get; private set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | BASE_DIR=src/OpenPr0gramm 4 | 5 | dotnet pack -c Release ${BASE_DIR}/OpenPr0gramm.csproj 6 | dotnet nuget push -k $NUGET_KEY -s https://api.nuget.org/v3/index.json ${BASE_DIR}/bin/Release/*.nupkg 7 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/User/Response/GetUserNameResponse.cs: -------------------------------------------------------------------------------- 1 | namespace OpenPr0gramm.Endpoint.User.Response 2 | { 3 | public class GetUserNameResponse : Pr0grammResponse 4 | { 5 | public string Name { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/ItemTagVote.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | #if FW 6 | [Serializable] 7 | #endif 8 | public class ItemTagVote 9 | { 10 | public string User { get; set; } 11 | public Vote Vote { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Pr0miumProducts.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace OpenPr0gramm 3 | { 4 | public static class Pr0miumProducts 5 | { 6 | public static string ThreeMonths { get; } = "pr0mium90"; 7 | public static string TwelveMonths { get; } = "pr0mium365"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/Model/InboxItemType.cs: -------------------------------------------------------------------------------- 1 | namespace OpenPr0gramm.Endpoint.Inbox.Model 2 | { 3 | #if FW 4 | [Serializable] 5 | #endif 6 | public enum InboxItemType 7 | { 8 | Message, 9 | Comment, 10 | Notification, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/TokenInfoResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | #if FW 6 | [Serializable] 7 | #endif 8 | public class TokenInfoResponse : Pr0grammResponse 9 | { 10 | public Token Token { get; private set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Generic/Response/IMessageResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace OpenPr0gramm.Generic.Response 5 | { 6 | public interface IMessageResponse 7 | { 8 | IReadOnlyList Messages { get; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetPaymentAddressResponse.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace OpenPr0gramm 3 | { 4 | public class GetPaymentAddressResponse : Pr0grammResponse 5 | { 6 | public string Address { get; private set; } 7 | public decimal Amount { get; private set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/ItemFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | [Flags] 6 | public enum ItemFlags 7 | { 8 | SFW = 1, 9 | NSFW = 2, 10 | NSFL = 4, 11 | NSFP = 8, 12 | AllPublic = SFW | NSFW | NSFL, 13 | All = AllPublic | NSFP 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/LogInResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | #if FW 6 | [Serializable] 7 | #endif 8 | public class LogInResponse : SuccessableResponse 9 | { 10 | public BanInfo Ban { get; private set; } 11 | public string Error { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/TokenResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | #if FW 6 | [Serializable] 7 | #endif 8 | public class TokenResponse : Pr0grammResponse 9 | { 10 | public string TokenError { get; private set; } 11 | public string Error { get; private set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/LoadInviteResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | #if FW 6 | [Serializable] 7 | #endif 8 | public class LoadInviteResponse : Pr0grammResponse 9 | { 10 | public InvitingUser Inviter { get; private set; } 11 | public string Email { get; private set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/ChangeEmailResponse.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | #if FW 7 | [Serializable] 8 | #endif 9 | public class ChangeUserDataResponse : Pr0grammResponse 10 | { 11 | public string Account { get; private set; } 12 | public string Error { get; private set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetDetailsResponse.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class GetDetailsResponse : Pr0grammResponse 11 | { 12 | public IReadOnlyList Tags { get; private set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetFollowListResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | #if FW 7 | [Serializable] 8 | #endif 9 | public class GetFollowListResponse : Pr0grammResponse 10 | { 11 | public IReadOnlyList List { get; private set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/LoggedInResponse.cs: -------------------------------------------------------------------------------- 1 | namespace OpenPr0gramm 2 | { 3 | public class LoggedInResponse 4 | { 5 | public bool LoggedIn { get; set; } 6 | public long TS { get; set; } 7 | public string Cache { get; set; } 8 | public int RT { get; set; } 9 | public int QC { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Service/IPr0grammContactService.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public interface IPr0grammContactService 7 | { 8 | [Post("/contact/send")] 9 | Task Send([Body(BodySerializationMethod.UrlEncoded)]ContactData data); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Service/IPr0grammPaypalService.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public interface IPr0grammPaypalService 7 | { 8 | [Post("/paypal/getcheckouturl")] 9 | Task GetCheckoutUrl([Body(BodySerializationMethod.UrlEncoded)]PaymentData data); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Service/IPr0grammBitcoinService.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public interface IPr0grammBitcoinService 7 | { 8 | [Post("/bitcoin/getpaymentaddress")] 9 | Task GetPaymentAddress([Body(BodySerializationMethod.UrlEncoded)]PaymentData data); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetItemsInfoResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | #if FW 7 | [Serializable] 8 | #endif 9 | public class GetItemsInfoResponse : Pr0grammResponse 10 | { 11 | public IReadOnlyList Tags { get; private set; } 12 | public IReadOnlyList Comments { get; private set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Token.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | #if FW 6 | [Serializable] 7 | #endif 8 | public class Token 9 | { 10 | // TODO 11 | public TokenProduct Product { get; set; } 12 | } 13 | 14 | #if FW 15 | [Serializable] 16 | #endif 17 | public class TokenProduct 18 | { 19 | // TODO 20 | public int Days { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: 2 | # http://EditorConfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | end_of_line = lf 12 | 13 | [*.cs] 14 | indent_size = 4 15 | 16 | [*.{md,json}] 17 | indent_size = 4 18 | indent_style = tab 19 | 20 | [*.csproj] 21 | indent_size = 2 22 | 23 | [*.nuspec] 24 | indent_size = 2 25 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetMessagesResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | #if FW 7 | [Serializable] 8 | #endif 9 | public class GetMessagesResponse : Pr0grammResponse 10 | { 11 | public IReadOnlyList Messages { get; private set; } 12 | public bool HasOlder { get; private set; } 13 | public bool HasNewer { get; private set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/SuccessableResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | #if FW 6 | [Serializable] 7 | #endif 8 | public class SuccessableResponse : Pr0grammResponse 9 | { 10 | public bool Success { get; private set; } 11 | 12 | public SuccessableResponse() 13 | { } 14 | public SuccessableResponse(bool success) 15 | { 16 | Success = success; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/FollowData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class FollowData : PostFormData 7 | { 8 | [AliasAs("name")] 9 | public string Name { get; } 10 | public FollowData(string nonce, string name) 11 | : base(nonce) 12 | { 13 | Debug.Assert(!string.IsNullOrWhiteSpace(name)); 14 | Name = name; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/InviteData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class InviteData : PostFormData 7 | { 8 | [AliasAs("email")] 9 | public string Email { get; } 10 | public InviteData(string nonce, string email) 11 | : base(nonce) 12 | { 13 | Debug.Assert(!string.IsNullOrWhiteSpace(email)); 14 | Email = email; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/Pr0grammResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using OpenPr0gramm.Json; 3 | using System; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | public class Pr0grammResponse 8 | { 9 | [JsonConverter(typeof(UnixDateTimeConverter))] 10 | public DateTime TS { get; private set; } 11 | public object Cache { get; private set; } 12 | public int RT { get; private set; } 13 | public int QC { get; private set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/Response/GetInboxCommentResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenPr0gramm.Endpoint.Inbox.Model; 3 | using OpenPr0gramm.Generic.Response; 4 | 5 | namespace OpenPr0gramm.Endpoint.Inbox.Response 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class GetInboxCommentResponse : Pr0grammResponse, IMessageResponse 11 | { 12 | public IReadOnlyList Messages { get; private set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/SendPasswordResetMailData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class SendPasswordResetMailData : AnonymousFormData 7 | { 8 | [AliasAs("email")] 9 | public string Email { get; } 10 | public SendPasswordResetMailData(string email) 11 | { 12 | Debug.Assert(!string.IsNullOrWhiteSpace(email)); 13 | Email = email; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/Response/GetInboxOverviewResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenPr0gramm.Endpoint.Inbox.Model; 3 | using OpenPr0gramm.Generic.Response; 4 | 5 | namespace OpenPr0gramm.Endpoint.Inbox.Response 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class GetInboxOverviewResponse : Pr0grammResponse, IMessageResponse 11 | { 12 | public IReadOnlyList Messages { get; private set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetItemsResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | #if FW 7 | [Serializable] 8 | #endif 9 | public class GetItemsResponse : Pr0grammResponse 10 | { 11 | public bool AtEnd { get; private set; } 12 | public bool AtStart { get; private set; } 13 | public object Error { get; private set; } 14 | public IReadOnlyList Items { get; private set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetUserInfoResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class GetUserInfoResponse : Pr0grammResponse 11 | { 12 | public AccountInfo Account { get; private set; } 13 | [JsonProperty(PropertyName = "invited")] 14 | public IReadOnlyList InvitedUsers { get; private set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/Response/GetInboxNotificationResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenPr0gramm.Endpoint.Inbox.Model; 3 | using OpenPr0gramm.Generic.Response; 4 | 5 | namespace OpenPr0gramm.Endpoint.Inbox.Response 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class GetInboxNotificationResponse : Pr0grammResponse, IMessageResponse 11 | { 12 | public IReadOnlyList Messages { get; private set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/TokenActionData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class TokenActionData : PostFormData 7 | { 8 | [AliasAs("token")] 9 | public string Token { get; } 10 | public TokenActionData(string nonce, string token) 11 | : base(nonce) 12 | { 13 | Debug.Assert(!string.IsNullOrWhiteSpace(token)); 14 | Token = token; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/VoteData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | public class VoteData : PostFormData 6 | { 7 | [AliasAs("id")] 8 | public int Id { get; } 9 | [AliasAs("vote")] 10 | public int AbsoluteVote { get; } 11 | public VoteData(string nonce, int id, int absoluteVote) 12 | : base(nonce) 13 | { 14 | Id = id; 15 | AbsoluteVote = absoluteVote; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/ChangeEmailData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class ChangeEmailData : PostFormData 7 | { 8 | [AliasAs("token")] 9 | public string Token { get; set; } 10 | public ChangeEmailData(string nonce, string token) 11 | : base(nonce) 12 | { 13 | Debug.Assert(!string.IsNullOrWhiteSpace(token)); 14 | Token = token; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/CaptchaResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | #if FW 6 | [Serializable] 7 | #endif 8 | public class CaptchaResponse : Pr0grammResponse 9 | { 10 | public string Token { get; private set; } 11 | public string Captcha { get; private set; } 12 | 13 | public byte[] GetCaptchaImageBytes() 14 | { 15 | return Convert.FromBase64String(Captcha.Substring(Captcha.IndexOf(',') + 1)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetCommentsResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | #if FW 7 | [Serializable] 8 | #endif 9 | public class GetCommentsResponse : Pr0grammResponse 10 | { 11 | public IReadOnlyList Comments { get; private set; } 12 | public bool HasOlder { get; private set; } 13 | public bool HasNewer { get; private set; } 14 | public CommentUser User { get; private set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/DeleteCommentData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | public class DeleteCommentData : PostFormData 6 | { 7 | [AliasAs("id")] 8 | public int CommentId { get; } 9 | [AliasAs("reason")] 10 | public string Reason { get; } 11 | public DeleteCommentData(string nonce, int commentId, string reason) 12 | : base(nonce) 13 | { 14 | CommentId = commentId; 15 | Reason = reason; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/Response/GetInboxConversationResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenPr0gramm.Endpoint.Inbox.Model; 3 | using OpenPr0gramm.Generic.Response; 4 | 5 | namespace OpenPr0gramm.Endpoint.Inbox.Response 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class GetInboxConversationResponse : Pr0grammResponse, IHasEndResponse 11 | { 12 | public IReadOnlyList Conversations { get; private set; } 13 | public bool AtEnd { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/LikedItem.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using OpenPr0gramm.Json; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class LikedItem : IPr0grammItem 7 | { 8 | public int Id { get; set; } 9 | /// Use the BaseAddress property of your HttpClient to prepend the protocol and host name. 10 | [JsonProperty(PropertyName = "thumb")] 11 | public string ThumbnailUrl { get; set; } 12 | 13 | public override string ToString() => $"{Id}, {ThumbnailUrl}"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/EditCommentData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | public class EditCommentData : PostFormData 6 | { 7 | [AliasAs("comment")] 8 | public string NewContent { get; } 9 | [AliasAs("commentId")] 10 | public int CommentId { get; } 11 | public EditCommentData(string nonce, int commentId, string newContent) 12 | : base(nonce) 13 | { 14 | CommentId = commentId; 15 | NewContent = newContent; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/LogOutData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class LogOutData : PostFormData 7 | { 8 | [AliasAs("id")] 9 | public string SessionId { get; } 10 | public LogOutData(string nonce, string sessionId) 11 | : base(nonce) 12 | { 13 | Debug.Assert(!string.IsNullOrWhiteSpace(sessionId)); 14 | Debug.Assert(sessionId.Length == 32); 15 | SessionId = sessionId; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/RequestEmailChangeData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class RequestEmailChangeData : ChangeAccountSettingData 7 | { 8 | [AliasAs("email")] 9 | public string NewEmail { get; } 10 | 11 | public RequestEmailChangeData(string nonce, string currentPassword, string newEmail) : base(nonce, currentPassword) 12 | { 13 | Debug.Assert(!string.IsNullOrWhiteSpace(newEmail)); 14 | NewEmail = newEmail; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/Response/GetInboxPrivateMessageResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenPr0gramm.Endpoint.Inbox.Model; 3 | using OpenPr0gramm.Generic.Response; 4 | 5 | namespace OpenPr0gramm.Endpoint.Inbox.Response 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class GetInboxPrivateMessageResponse : Pr0grammResponse, IHasEndResponse, IMessageResponse 11 | { 12 | public IReadOnlyList Messages { get; set; } 13 | public bool AtEnd { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/ChangeAccountSettingData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class ChangeAccountSettingData : PostFormData 7 | { 8 | [AliasAs("currentPassword")] 9 | public string CurrentPassword { get; } 10 | public ChangeAccountSettingData(string nonce, string currentPassword) 11 | : base(nonce) 12 | { 13 | Debug.Assert(!string.IsNullOrWhiteSpace(currentPassword)); 14 | CurrentPassword = currentPassword; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/PostFormData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public abstract class PostFormData 7 | { 8 | [AliasAs("_nonce")] 9 | public string Nonce { get; } 10 | protected PostFormData(string nonce) 11 | { 12 | Debug.Assert(!string.IsNullOrWhiteSpace(nonce)); 13 | Debug.Assert(nonce.Length == 16); 14 | Nonce = nonce; 15 | } 16 | } 17 | public abstract class AnonymousFormData 18 | { 19 | // No Nonce 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/BanInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using OpenPr0gramm.Json; 3 | using System; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class BanInfo 11 | { 12 | [JsonProperty(PropertyName = "banned")] 13 | public bool IsBanned { get; set; } 14 | [JsonProperty(PropertyName = "till")] 15 | [JsonConverter(typeof(UnixDateTimeConverter))] 16 | public DateTime Until { get; set; } 17 | [JsonProperty(PropertyName = "reason")] 18 | public string Reason { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/UserMark.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace OpenPr0gramm 3 | { 4 | public enum UserMark 5 | { 6 | Schwuchtel = 0, 7 | Neuschwuchtel = 1, 8 | Altschwuchtel = 2, 9 | Administrator = 3, 10 | Gebannt = 4, 11 | Moderator = 5, 12 | Fliesentisch = 6, 13 | LebendeLegende = 7, 14 | Wichtel = 8, 15 | EdlerSpender = 9, 16 | Mittelaltschwuchtel = 10, 17 | AltModerator = 11, 18 | CommunityHelfer = 12, 19 | NutzerBot = 13, 20 | SystemBot = 14, 21 | AltHelfer = 15 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/FormData/PrivateMessageData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | 3 | namespace OpenPr0gramm.Endpoint.Inbox.FormData 4 | { 5 | public class PrivateMessageData : PostFormData 6 | { 7 | [AliasAs("recipientId")] 8 | public int RecipientId { get; } 9 | [AliasAs("comment")] 10 | public string Content { get; } 11 | 12 | public PrivateMessageData(string nonce, int recipientId, string message) 13 | : base(nonce) 14 | { 15 | RecipientId = recipientId; 16 | Content = message; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/ProfileUpload.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using OpenPr0gramm.Json; 3 | using System; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class ProfileUpload : IPr0grammItem 11 | { 12 | public int Id { get; set; } 13 | /// Use the BaseAddress property of your HttpClient to prepend the protocol and host name. 14 | [JsonProperty(PropertyName = "thumb")] 15 | public string ThumbnailUrl { get; set; } 16 | 17 | public override string ToString() => $"{Id}, {ThumbnailUrl}"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/ChangePasswordData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class ChangePasswordData : ChangeAccountSettingData 7 | { 8 | [AliasAs("password")] 9 | public string NewPassword 10 | { 11 | get; 12 | } 13 | 14 | public ChangePasswordData(string nonce, string currentPassword, string newPassword): base (nonce, currentPassword) 15 | { 16 | Debug.Assert(!string.IsNullOrWhiteSpace(newPassword)); 17 | Debug.Assert(newPassword.Length >= 6); 18 | NewPassword = newPassword; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | - Please don't restructure the entire project or do other large changes because something is not right in your opinion. It may be bad, but please have a chat with us _beforehand_. 2 | - Keep the project formatting. Plase don't change a global formatting option just because it's the way you do it. Don't auto-format parts of the code that you did not change. 3 | - Try to keep a PR address _a single thing_, so we can test and deploy independently (don't resolve multiple issues with a single PR). 4 | - [Keep things simple](https://en.wikipedia.org/wiki/KISS_principle), even if Netflix solves things cooler. We're not Netflix. [Do it the boring way](http://boringtechnology.club). 5 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/Model/InboxConversation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using OpenPr0gramm.Json; 4 | 5 | namespace OpenPr0gramm.Endpoint.Inbox.Model 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class InboxConversation 11 | { 12 | public UserMark Mark { get; set; } 13 | 14 | public string Name { get; set; } 15 | 16 | [JsonProperty(PropertyName = "lastMessage")] 17 | [JsonConverter(typeof(UnixDateTimeConverter))] 18 | public DateTime LastMessageAt { get; set; } 19 | 20 | public int UnreadCount { get; set; } 21 | 22 | public bool Blocked { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/PaymentData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | public class PaymentData : PostFormData 6 | { 7 | [AliasAs("email")] 8 | public string EmailAddress { get; } 9 | [AliasAs("tos")] 10 | public bool TermsAccepted { get; } 11 | [AliasAs("product")] 12 | public string Product { get; } 13 | 14 | public PaymentData(string nonce, string emailAddress, string product, bool termsAccepted) 15 | : base(nonce) 16 | { 17 | EmailAddress = emailAddress; 18 | Product = product; 19 | TermsAccepted = termsAccepted; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/CD.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - "!*" 7 | tags: 8 | - "v*" 9 | 10 | jobs: 11 | deploy: 12 | name: Deploy 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 6.x 21 | 22 | - run: dotnet restore 23 | - run: dotnet build --no-restore 24 | - run: dotnet test --no-build --verbosity normal 25 | - run: dotnet pack -c Release src/OpenPr0gramm/OpenPr0gramm.csproj 26 | - run: dotnet nuget push -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json src/OpenPr0gramm/bin/Release/*.nupkg 27 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/BanData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class BanData : PostFormData 7 | { 8 | [AliasAs("user")] 9 | public string User { get; set; } 10 | [AliasAs("reason")] 11 | public string Reason { get; set; } 12 | [AliasAs("days")] 13 | public int Days { get; set; } 14 | public BanData(string nonce, string user, string reason, int days) 15 | : base(nonce) 16 | { 17 | Debug.Assert(!string.IsNullOrWhiteSpace(user)); 18 | User = user; 19 | Reason = reason; 20 | Days = days; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Service/IPr0grammTagsService.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public interface IPr0grammTagsService 7 | { 8 | [Post("/tags/add")] 9 | Task Add([Body(BodySerializationMethod.UrlEncoded)]AddTagsData data); 10 | 11 | [Post("/tags/delete")] 12 | Task Delete([Body(BodySerializationMethod.UrlEncoded)]DeleteTagsData data); 13 | 14 | [Get("/tags/details")] 15 | Task GetDetails(int itemId); 16 | 17 | [Post("/tags/vote")] 18 | Task Vote([Body(BodySerializationMethod.UrlEncoded)]VoteData data); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/AddTagsData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class AddTagsData : PostFormData 7 | { 8 | [AliasAs("itemId")] 9 | public int ItemId { get; } 10 | [AliasAs("submit")] 11 | public string Submit { get; } 12 | [AliasAs("tags")] 13 | public string Tags { get; set; } 14 | 15 | public AddTagsData(string nonce, int itemId, params string[] tags) 16 | : base(nonce) 17 | { 18 | Debug.Assert(tags != null); 19 | Tags = string.Join(",", tags); 20 | ItemId = itemId; 21 | Submit = "Tags speichern"; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Model/SyncAction.cs: -------------------------------------------------------------------------------- 1 | namespace OpenPr0gramm.Model 2 | { 3 | public enum SyncAction 4 | { 5 | VoteItemDown = 1, 6 | VoteItemNeutral = 2, 7 | VoteItemUp = 3, 8 | VoteCommentDown = 4, 9 | VoteCommentNeutral = 5, 10 | VoteCommentUp = 6, 11 | VoteTagDown = 7, 12 | VoteTagNeutral = 8, 13 | VoteTagUp = 9, 14 | 15 | FollowStateFollow1 = 12, 16 | FollowStateNone = 13, 17 | FollowStateSubscribed = 14, 18 | FollowStateFollow2 = 15, 19 | 20 | CommentFav = 16, 21 | CommentUnFav = 17, 22 | 23 | ItemUnCollect = 18, 24 | ItemCollect = 19, 25 | CollectionId = 20 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | name: Build 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Setup .NET 24 | uses: actions/setup-dotnet@v1 25 | with: 26 | dotnet-version: 6.x 27 | 28 | - name: Restore dependencies 29 | run: dotnet restore 30 | - name: Build 31 | run: dotnet build --no-restore 32 | - name: Test 33 | run: dotnet test --no-build --verbosity normal 34 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/Model/InboxPrivateMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using OpenPr0gramm.Json; 4 | 5 | namespace OpenPr0gramm.Endpoint.Inbox.Model 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class InboxPrivateMessage 11 | { 12 | public int Id { get; set; } 13 | 14 | public bool Sent { get; set; } 15 | 16 | public int Read { get; set; } 17 | 18 | public string Name { get; set; } 19 | 20 | public UserMark Mark { get; set; } 21 | 22 | [JsonProperty(PropertyName = "created")] 23 | [JsonConverter(typeof(UnixDateTimeConverter))] 24 | public DateTime CreatedAt { get; set; } 25 | 26 | public string Message { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/OpenPr0gramm.Tests/OpenPr0gramm.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | OpenPr0gramm.Tests 10 | OpenPr0gramm.Tests 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/AccountInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using OpenPr0gramm.Json; 3 | using System; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class AccountInfo 11 | { 12 | public bool LikesArePublic { get; set; } 13 | public string Email { get; set; } 14 | [JsonProperty(PropertyName = "invites")] 15 | public int InviteCount { get; set; } 16 | public UserMark Mark { get; set; } 17 | public UserMark MarkDefault { get; set; } 18 | [JsonProperty(PropertyName = "paidUntil")] 19 | [JsonConverter(typeof(UnixDateTimeConverter))] 20 | public DateTime PaidUntil { get; set; } 21 | 22 | [JsonIgnore] 23 | public bool IsPaidMembership => PaidUntil > DateTime.Now; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/SiteSettingsData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class SiteSettingsData : PostFormData 7 | { 8 | [AliasAs("likesArePublic")] 9 | public int LikesArePublic { get; } 10 | [AliasAs("showAds")] 11 | public int ShowAds { get; } 12 | [AliasAs("userStatus")] 13 | public string UserStatus { get; } 14 | public SiteSettingsData(string nonce, bool likesArePublic, bool showAds, string userStatus) 15 | : base(nonce) 16 | { 17 | Debug.Assert(!string.IsNullOrWhiteSpace(userStatus)); 18 | LikesArePublic = likesArePublic ? 1 : 0; 19 | ShowAds = showAds ? 1 : 0; 20 | UserStatus = userStatus; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/LogInData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class LogInData 7 | { 8 | [AliasAs("name")] 9 | public string Name { get; set; } 10 | [AliasAs("password")] 11 | public string Password { get; set; } 12 | [AliasAs("token")] 13 | public string Token { get; set; } 14 | [AliasAs("captcha")] 15 | public string Captcha { get; set; } 16 | public LogInData(string name, string password, string captchaToken, string captchaSolution) 17 | { 18 | Debug.Assert(!string.IsNullOrWhiteSpace(name)); 19 | Name = name; 20 | Password = password; 21 | Token = captchaToken; 22 | Captcha = captchaSolution; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/PostCommentData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | public class PostCommentData : PostFormData 6 | { 7 | public const int NoParentId = 0; 8 | [AliasAs("comment")] 9 | public string Content { get; } 10 | [AliasAs("parentId")] 11 | public int ParentId { get; } 12 | [AliasAs("itemId")] 13 | public int ItemId { get; } 14 | public PostCommentData(string nonce, int itemId, string content) 15 | : this(nonce, itemId, content, NoParentId) 16 | { } 17 | public PostCommentData(string nonce, int itemId, string content, int parentId) 18 | : base(nonce) 19 | { 20 | Content = content; 21 | ItemId = itemId; 22 | ParentId = parentId; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/OpenPr0gramm.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | 8 | OpenPr0gramm 9 | OpenPr0gramm 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers 15 | all 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 1.1.1 9 | 1.1.1.0 10 | 1.1.1 11 | holzmaster 12 | en 13 | Open source .NET API for pr0gramm 14 | LGPL-3.0-or-later 15 | https://pr0gramm.com/media/pr0gramm-favicon.png 16 | https://github.com/holzmaster/OpenPr0gramm 17 | https://github.com/holzmaster/OpenPr0gramm.git 18 | pr0gramm open api wrapper json http 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/ContactData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class ContactData : PostFormData 7 | { 8 | [AliasAs("email")] 9 | public string EmailAddress { get; } 10 | [AliasAs("subject")] 11 | public string Subject { get; } 12 | [AliasAs("message")] 13 | public string Message { get; } 14 | 15 | public ContactData(string nonce, string email, string subject, string message) 16 | : base(nonce) 17 | { 18 | Debug.Assert(!string.IsNullOrWhiteSpace(email)); 19 | Debug.Assert(!string.IsNullOrWhiteSpace(subject)); 20 | Debug.Assert(!string.IsNullOrWhiteSpace(message)); 21 | EmailAddress = email; 22 | Subject = subject; 23 | Message = message; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/ResetPasswordData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class ResetPasswordData : AnonymousFormData 7 | { 8 | [AliasAs("name")] 9 | public string Name { get; } 10 | [AliasAs("token")] 11 | public string Token { get; } 12 | [AliasAs("password")] 13 | public string NewPassword { get; } 14 | public ResetPasswordData(string token, string name, string newPassword) 15 | { 16 | Debug.Assert(!string.IsNullOrWhiteSpace(token)); 17 | Debug.Assert(!string.IsNullOrWhiteSpace(name)); 18 | Debug.Assert(!string.IsNullOrEmpty(newPassword)); 19 | Debug.Assert(newPassword.Length >= 6); 20 | Token = token; 21 | Name = name; 22 | NewPassword = newPassword; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Service/IPr0grammProfileService.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public interface IPr0grammProfileService 7 | { 8 | [Get("/profile/comments")] 9 | Task GetCommentsBefore(string name, ItemFlags flags, int before); 10 | 11 | [Get("/profile/comments")] 12 | Task GetCommentsAfter(string name, ItemFlags flags, int after); 13 | 14 | [Post("/profile/follow")] 15 | Task Follow([Body(BodySerializationMethod.UrlEncoded)]FollowData data); 16 | 17 | [Get("/profile/info")] 18 | Task GetInfo(string name, ItemFlags flags); 19 | 20 | [Post("/profile/unfollow")] 21 | Task Unfollow([Body(BodySerializationMethod.UrlEncoded)]FollowData data); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/SyncResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | using OpenPr0gramm.Json; 4 | using OpenPr0gramm.Model; 5 | 6 | namespace OpenPr0gramm 7 | { 8 | public class SyncResponse : Pr0grammResponse 9 | { 10 | public SyncInboxResponse Inbox { get; private set; } 11 | 12 | [JsonConverter(typeof(SyncLogItemConverter))] 13 | [JsonProperty(PropertyName = "log")] 14 | public IEnumerable LogItems { get; private set; } 15 | 16 | public int LogLength { get; private set; } 17 | 18 | public int Score { get; private set; } 19 | } 20 | 21 | public class SyncInboxResponse 22 | { 23 | public int Comments { get; set; } 24 | public int Mentions { get; set; } 25 | public int Messages { get; set; } 26 | public int Notifications { get; set; } 27 | public int Follows { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/DeleteTagsData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Collections.Generic; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class DeleteTagsData : PostFormData 7 | { 8 | [AliasAs("tags[]")] 9 | public IReadOnlyList TagIds { get; } 10 | [AliasAs("banUsers")] 11 | public bool BanUsers { get; } 12 | [AliasAs("itemId")] 13 | public int ItemId { get; } 14 | [AliasAs("days")] 15 | public int Days { get; } 16 | public DeleteTagsData(string nonce, int itemId, bool banUsers, int[] tagIds) 17 | : this(nonce, itemId, banUsers, 1, tagIds) 18 | { } 19 | public DeleteTagsData(string nonce, int itemId, bool banUsers, int days, int[] tagIds) 20 | : base(nonce) 21 | { 22 | ItemId = itemId; 23 | BanUsers = banUsers; 24 | Days = days; 25 | TagIds = tagIds; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Json/ImageUrlConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace OpenPr0gramm.Json 5 | { 6 | internal class ImageUrlConverter : JsonConverter 7 | { 8 | public override bool CanConvert(Type objectType) => objectType == typeof(string); 9 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 10 | { 11 | if (reader.Value is string value && !string.IsNullOrEmpty(value)) 12 | { 13 | var s = !value.StartsWith("/"); 14 | return $"{ClientConstants.GetImageUrlPrefix(true)}{(s ? "/" : "")}{value}"; 15 | } 16 | 17 | return string.Empty; 18 | } 19 | 20 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Service/IPr0grammCommentsService.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public interface IPr0grammCommentsService 7 | { 8 | [Post("/comments/delete")] 9 | Task Delete([Body(BodySerializationMethod.UrlEncoded)]DeleteCommentData data); 10 | 11 | [Post("/comments/edit")] 12 | Task Edit([Body(BodySerializationMethod.UrlEncoded)]EditCommentData data); 13 | 14 | [Post("/comments/post")] 15 | Task Post([Body(BodySerializationMethod.UrlEncoded)]PostCommentData data); 16 | 17 | [Post("/comments/softDelete")] 18 | Task SoftDelete([Body(BodySerializationMethod.UrlEncoded)]DeleteCommentData data); 19 | 20 | [Post("/comments/vote")] 21 | Task Vote([Body(BodySerializationMethod.UrlEncoded)]VoteData data); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Json/ThumbUrlConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace OpenPr0gramm.Json 5 | { 6 | internal class ThumbUrlConverter : JsonConverter 7 | { 8 | public override bool CanConvert(Type objectType) => objectType == typeof(string); 9 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 10 | { 11 | if (reader.Value is string value && !string.IsNullOrEmpty(value)) 12 | { 13 | var s = !value.StartsWith("/"); 14 | return $"{ClientConstants.GetThumbnailUrlPrefix(true)}{(s ? "/" : "")}{value}"; 15 | } 16 | 17 | return string.Empty; 18 | } 19 | 20 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Json/UnixDateTimeConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace OpenPr0gramm.Json 5 | { 6 | internal class UnixDateTimeConverter : JsonConverter 7 | { 8 | public override bool CanConvert(Type objectType) => objectType == typeof(DateTime); 9 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 10 | { 11 | var t = reader.Value is long value 12 | ? value 13 | : long.Parse((string)reader.Value); 14 | 15 | return DateTimeOffset.FromUnixTimeSeconds(t).DateTime; 16 | } 17 | 18 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 19 | { 20 | if (!(value is DateTime)) 21 | throw new ArgumentException("Expected date object value."); 22 | long ticks = ((DateTime)value).ToUnixTime(); 23 | writer.WriteValue(ticks); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Tag.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class Tag : IPr0grammTag 11 | { 12 | public int Id { get; set; } 13 | [JsonProperty(PropertyName = "tag")] 14 | public string Content { get; set; } 15 | public float Confidence { get; set; } 16 | 17 | public override string ToString() => $"{Content} ({Id}, {Confidence})"; 18 | } 19 | 20 | #if FW 21 | [Serializable] 22 | #endif 23 | public class ItemTagDetails : Tag 24 | { 25 | public string User { get; set; } 26 | [JsonProperty(PropertyName = "up")] 27 | public int Upvotes { get; set; } 28 | [JsonProperty(PropertyName = "down")] 29 | public int Downvotes { get; set; } 30 | public IReadOnlyList Votes { get; set; } 31 | } 32 | 33 | public interface IPr0grammTag 34 | { 35 | int Id { get; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/DeleteItemData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | public class DeleteItemData : PostFormData 6 | { 7 | [AliasAs("itemId")] 8 | public int ItemId { get; } 9 | [AliasAs("days")] 10 | public int Days { get; } 11 | [AliasAs("banUser")] 12 | public bool BanUser { get; } 13 | [AliasAs("notifyUser")] 14 | public bool NotifyUser { get; } 15 | [AliasAs("reason")] 16 | public string Reason { get; } 17 | [AliasAs("customReason")] 18 | public string CustomReason { get; } 19 | 20 | public DeleteItemData(string nonce, int itemId, string reason, string customReason, bool notifyUser, bool banUser, int days) 21 | : base(nonce) 22 | { 23 | ItemId = itemId; 24 | Reason = reason; 25 | CustomReason = customReason; 26 | NotifyUser = notifyUser; 27 | BanUser = banUser; 28 | Days = days; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/IPr0grammInboxService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using OpenPr0gramm.Endpoint.Inbox.FormData; 3 | using OpenPr0gramm.Endpoint.Inbox.Response; 4 | using Refit; 5 | 6 | namespace OpenPr0gramm.Endpoint.Inbox 7 | { 8 | public interface IPr0grammInboxService 9 | { 10 | [Get("/inbox/all")] 11 | Task GetOverview(); 12 | 13 | [Get("/inbox/comments")] 14 | Task GetComments(); 15 | 16 | [Get("/inbox/notifications")] 17 | Task GetNotifications(); 18 | 19 | //TODO [Get("/inbox/follows")] 20 | 21 | [Get("/inbox/conversations")] 22 | Task GetConversations(long? older); 23 | 24 | [Get("/inbox/messages")] 25 | Task GetMessages(string with, long? older); 26 | 27 | [Post("/inbox/post")] 28 | Task PostMessage([Body(BodySerializationMethod.UrlEncoded)]PrivateMessageData data); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/FormData/JoinWithTokenData.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Diagnostics; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public class JoinWithTokenData : AnonymousFormData 7 | { 8 | [AliasAs("token")] 9 | public string Token { get; } 10 | [AliasAs("password")] 11 | public string Password { get; } 12 | [AliasAs("email")] 13 | public string Email { get; } 14 | [AliasAs("name")] 15 | public string Name { get; } 16 | 17 | public JoinWithTokenData(string email, string name, string password, string token) 18 | { 19 | Debug.Assert(!string.IsNullOrWhiteSpace(token)); 20 | Debug.Assert(!string.IsNullOrWhiteSpace(name)); 21 | Debug.Assert(name.Length >= 2); 22 | Debug.Assert(!string.IsNullOrWhiteSpace(email)); 23 | Debug.Assert(!string.IsNullOrEmpty(password)); 24 | Debug.Assert(password.Length >= 6); 25 | Token = token; 26 | Name = name; 27 | Email = email; 28 | Password = password; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/DateTimeEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenPr0gramm 4 | { 5 | /// Some utility extensions on . 6 | internal static class DateTimeExtensions 7 | { 8 | public static long ToUnixTime(this DateTime value) 9 | { 10 | var totalSeconds = (value.ToUniversalTime() - DateTimeEx.UnixStart).TotalSeconds; 11 | return (long)Math.Truncate(totalSeconds); 12 | } 13 | } 14 | 15 | internal static class DateTimeEx 16 | { 17 | internal static readonly DateTime UnixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); 18 | 19 | public static DateTime FromUnixToUtcDateTime(int unixTime) => FromUnixToUtcDateTime((long)unixTime); 20 | public static DateTime FromUnixToUtcDateTime(long unixTime) => UnixStart.AddSeconds(unixTime); 21 | 22 | 23 | // TODO: Tests 24 | public static DateTime FromUnixToLocalTime(int unixTime) => FromUnixToLocalTime((long)unixTime); 25 | public static DateTime FromUnixToLocalTime(long unixTime) => FromUnixToUtcDateTime(unixTime).ToLocalTime(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/LoggingMessageHandler.cs: -------------------------------------------------------------------------------- 1 | #if DEBUG && FW 2 | 3 | using System.Diagnostics; 4 | using System.Net.Http; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace OpenPr0gramm 9 | { 10 | internal class LoggingMessageHandler : DelegatingHandler 11 | { 12 | public LoggingMessageHandler(HttpMessageHandler innerHandler) 13 | : base(innerHandler) 14 | { } 15 | 16 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 17 | { 18 | Trace.WriteLine("Request:"); 19 | Trace.WriteLine(request.ToString()); 20 | if (request.Content != null) 21 | Trace.WriteLine(await request.Content.ReadAsStringAsync()); 22 | 23 | var response = await base.SendAsync(request, cancellationToken); 24 | 25 | Trace.WriteLine("Response:"); 26 | Trace.WriteLine(response.ToString()); 27 | if (response.Content != null) 28 | Trace.WriteLine(await response.Content.ReadAsStringAsync()); 29 | return response; 30 | } 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/EnumsAsIntegersParameterFormatter.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System; 3 | using System.Reflection; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | internal class EnumsAsIntegersParameterFormatter : IUrlParameterFormatter 8 | { 9 | // See: https://github.com/paulcbetts/refit/issues/184#issuecomment-137324961 10 | public string Format(object parameterValue, ParameterInfo parameterInfo) 11 | { 12 | if (parameterValue == null) 13 | return null; 14 | var info = Denullify(parameterInfo.ParameterType); 15 | if (info.IsEnum && parameterInfo.ParameterType == typeof(ItemFlags)) 16 | return ((int)((ItemFlags)parameterValue)).ToString(); 17 | return parameterValue.ToString(); 18 | } 19 | 20 | // Just makes sure you have the generic type arg for Nullable 21 | static TypeInfo Denullify(Type type) 22 | { 23 | var info = type.GetTypeInfo(); 24 | if (info.IsGenericType && info.GetGenericTypeDefinition() == typeof(Nullable<>)) 25 | return type.GenericTypeArguments[0].GetTypeInfo(); 26 | return info; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Response/GetProfileInfoResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class GetProfileInfoResponse : Pr0grammResponse 11 | { 12 | public User User { get; private set; } 13 | public IReadOnlyList Comments { get; private set; } 14 | public int CommentCount { get; private set; } 15 | public IReadOnlyList Uploads { get; private set; } 16 | public int UploadCount { get; private set; } 17 | public bool LikesArePublic { get; private set; } 18 | public IReadOnlyList Likes { get; private set; } 19 | public int LikeCount { get; private set; } 20 | public int TagCount { get; private set; } 21 | public IReadOnlyList Badges { get; internal set; } 22 | [JsonProperty(PropertyName = "followcount")] 23 | public int FollowCount { get; private set; } 24 | [JsonProperty(PropertyName = "following")] 25 | public bool IsFollowing { get; private set; } 26 | 27 | public override string ToString() => $"Profile: {User}; {Comments.Count} comments (of {CommentCount}); {Uploads.Count} comments (of {UploadCount}); {TagCount} tags"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/ClientConstants.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace OpenPr0gramm 3 | { 4 | internal static class ClientConstants 5 | { 6 | internal const string OpenPr0grammVersion = "1.0.0"; 7 | 8 | internal const string ProtocolPrefix = "https://"; 9 | internal const string InsecureProtocolPrefix = "http://"; 10 | 11 | internal const string HostName = "pr0gramm.com"; 12 | 13 | internal const string BaseAddress = ProtocolPrefix + HostName; 14 | internal const string InsecureBaseAddress = InsecureProtocolPrefix + HostName; 15 | 16 | internal const string ApiBaseUrl = BaseAddress + "/api"; 17 | 18 | // c# cannot evaluate functions at compile time (like C++ value templates), this is not cool. :( 19 | public static string GetUserAgent(string component) => $"OpenPr0gramm/{OpenPr0grammVersion} ({component})"; 20 | 21 | private static string GetPrefix(bool secure) => secure ? ProtocolPrefix : InsecureProtocolPrefix; 22 | 23 | internal static string GetImageUrlPrefix(bool secure) => GetPrefix(secure) + "img." + HostName; 24 | internal static string GetThumbnailUrlPrefix(bool secure) => GetPrefix(secure) + "thumb." + HostName; 25 | internal static string GetFullSizeUrlPrefix(bool secure) => GetPrefix(secure) + "full." + HostName; 26 | 27 | internal const string BadgeUrlPrefix = ProtocolPrefix + HostName + "/media/badges"; 28 | internal const string UserUrlPrefix = ProtocolPrefix + HostName + "/user"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Endpoint/Inbox/Model/InboxItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using OpenPr0gramm.Json; 4 | 5 | namespace OpenPr0gramm.Endpoint.Inbox.Model 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class InboxItem 11 | { 12 | public InboxItemType Type { get; set; } 13 | 14 | public int Id { get; set; } 15 | 16 | public int ItemId { get; set; } 17 | 18 | [JsonConverter(typeof(ImageUrlConverter))] 19 | [JsonProperty(PropertyName = "image")] 20 | public string ImageUrl { get; set; } 21 | 22 | [JsonConverter(typeof(ThumbUrlConverter))] 23 | [JsonProperty(PropertyName = "thumb")] 24 | public string ThumbnailUrl { get; set; } 25 | 26 | public ItemFlags Flags { get; set; } 27 | 28 | public string Name { get; set; } 29 | 30 | public UserMark Mark { get; set; } 31 | 32 | public int SenderId { get; set; } 33 | 34 | public int Score { get; set; } 35 | 36 | public object Collection { get; set; } // TODO 37 | 38 | public string Owner { get; set; } 39 | 40 | public UserMark? OwnerMark { get; set; } 41 | 42 | public string Keyword { get; set; } 43 | 44 | [JsonProperty(PropertyName = "created")] 45 | [JsonConverter(typeof(UnixDateTimeConverter))] 46 | public DateTime CreatedAt { get; set; } 47 | 48 | public string Message { get; set; } 49 | 50 | public bool Read { get; set; } 51 | 52 | public bool Blocked { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/JsonNet.PrivateSettersContractResolvers/PrivateSettersContractResolvers.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Serialization; 4 | 5 | // ReSharper disable once CheckNamespace 6 | namespace JsonNet.PrivateSettersContractResolvers 7 | { 8 | public class PrivateSetterContractResolver : DefaultContractResolver 9 | { 10 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) 11 | { 12 | var jProperty = base.CreateProperty(member, memberSerialization); 13 | if (jProperty.Writable) 14 | return jProperty; 15 | 16 | jProperty.Writable = member.IsPropertyWithSetter(); 17 | 18 | return jProperty; 19 | } 20 | } 21 | 22 | public class PrivateSetterCamelCasePropertyNamesContractResolver : CamelCasePropertyNamesContractResolver 23 | { 24 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) 25 | { 26 | var jProperty = base.CreateProperty(member, memberSerialization); 27 | if (jProperty.Writable) 28 | return jProperty; 29 | 30 | jProperty.Writable = member.IsPropertyWithSetter(); 31 | 32 | return jProperty; 33 | } 34 | } 35 | 36 | internal static class MemberInfoExtensions 37 | { 38 | internal static bool IsPropertyWithSetter(this MemberInfo member) 39 | { 40 | var property = member as PropertyInfo; 41 | 42 | return property?.GetSetMethod(true) != null; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/OpenPr0gramm/Comment.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using OpenPr0gramm.Json; 3 | using System; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | // TODO: Check this 8 | #if FW 9 | [Serializable] 10 | #endif 11 | public abstract class Comment : IPr0grammComment 12 | { 13 | public int Id { get; set; } 14 | [JsonProperty(PropertyName = "up")] 15 | public int Upvotes { get; set; } 16 | [JsonProperty(PropertyName = "down")] 17 | public int Downvotes { get; set; } 18 | [JsonProperty(PropertyName = "created")] 19 | [JsonConverter(typeof(UnixDateTimeConverter))] 20 | public DateTime CreatedAt { get; set; } 21 | public string Content { get; set; } 22 | public override string ToString() => $"{Id}, {Upvotes}/{Downvotes}, {Content}"; 23 | } 24 | 25 | #if FW 26 | [Serializable] 27 | #endif 28 | public class ProfileComment : Comment 29 | { 30 | public int ItemId { get; set; } 31 | /// Use the BaseAddress property of your HttpClient to prepend the protocol and host name. 32 | [JsonProperty(PropertyName = "thumb")] 33 | public string ThumbnailUrl { get; set; } 34 | public override string ToString() => "Profile: " + base.ToString(); 35 | } 36 | 37 | #if FW 38 | [Serializable] 39 | #endif 40 | public class ItemComment : Comment 41 | { 42 | [JsonProperty(PropertyName = "parent")] 43 | public int ParentId { get; set; } 44 | public float Confidence { get; set; } 45 | public string Name { get; set; } 46 | public UserMark Mark { get; set; } 47 | public override string ToString() => "Item: " + base.ToString(); 48 | } 49 | 50 | public interface IPr0grammComment 51 | { 52 | int Id { get; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Json/SyncLogItemConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using OpenPr0gramm.Model; 5 | 6 | namespace OpenPr0gramm.Json 7 | { 8 | internal class SyncLogItemConverter : JsonConverter 9 | { 10 | public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable); 11 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 12 | { 13 | var items = new List(); 14 | 15 | if (reader.Value is string value && !string.IsNullOrEmpty(value)) 16 | { 17 | var bytes = Convert.FromBase64String(value); 18 | if (bytes.Length % 5 != 0) 19 | { 20 | throw new ArgumentException("Log length out of range"); 21 | } 22 | 23 | var actions = bytes.Length / 5; 24 | 25 | for (var i = 0; i < actions; i++) 26 | { 27 | var id = BitConverter.ToInt32(bytes, i * 5); 28 | var action = bytes[i * 5 + 4]; 29 | 30 | items.Add(new SyncLogItem 31 | { 32 | Action = (SyncAction) action, 33 | Id = id 34 | }); 35 | } 36 | } 37 | 38 | return items.ToArray(); 39 | } 40 | 41 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 42 | { 43 | var log = new List(); 44 | if (value is IEnumerable items) 45 | { 46 | foreach (var item in items) 47 | { 48 | log.AddRange(BitConverter.GetBytes(item.Id)); 49 | log.Add((byte)item.Action); 50 | } 51 | } 52 | 53 | writer.WriteValue(Convert.ToBase64String(log.ToArray())); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Service/IPr0grammItemsService.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | 4 | namespace OpenPr0gramm 5 | { 6 | public interface IPr0grammItemsService 7 | { 8 | [Post("/items/delete")] 9 | Task Delete([Body(BodySerializationMethod.UrlEncoded)]DeleteItemData data); 10 | 11 | [Get("/items/get")] 12 | Task GetItems(ItemFlags flags, int promoted, int following, string tags, string user, string collection, int self); 13 | [Get("/items/get")] 14 | Task GetItemsNewer(ItemFlags flags, int promoted, int following, string tags, string user, string collection, int self, int newer); 15 | [Get("/items/get")] 16 | Task GetItemsOlder(ItemFlags flags, int promoted, int following, string tags, string user, string collection, int self, int older); 17 | [Get("/items/get")] 18 | Task GetItemsAround(ItemFlags flags, int promoted, int following, string tags, string user, string collection, int self, [AliasAs("id")] int aroundId); 19 | 20 | [Get("/items/info")] 21 | Task GetInfo(int itemId); 22 | 23 | /* 24 | [Post("/items/post")] 25 | Task Post([Body(BodySerializationMethod.UrlEncoded)]PostItemData data); 26 | 27 | [Multipart] 28 | [Post("/items/upload")] 29 | Task Upload(FileInfo file); 30 | */ 31 | 32 | [Post("/items/ratelimited")] 33 | Task RateLimited(); 34 | 35 | [Post("/items/vote")] 36 | Task Vote([Body(BodySerializationMethod.UrlEncoded)]VoteData data); 37 | } 38 | 39 | /* 40 | #if FW 41 | [Serializable] 42 | #endif 43 | public class UploadResponse 44 | { 45 | public string Key { get; set; } 46 | } 47 | 48 | public class PostItemData : PostFormData 49 | { 50 | [AliasAs("sfwstatus")] 51 | public string SfwStatus { get; } 52 | [AliasAs("tags")] 53 | public string Tags { get; } 54 | [AliasAs("checkSimilar")] 55 | public int CheckSimilar { get; } 56 | [AliasAs("key")] 57 | public string Key { get; } 58 | 59 | public PostItemData(string nonce) 60 | : base(nonce) 61 | { 62 | //TODO 63 | } 64 | } 65 | */ 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Item.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using OpenPr0gramm.Json; 3 | using System; 4 | using System.Diagnostics; 5 | 6 | namespace OpenPr0gramm 7 | { 8 | #if FW 9 | [Serializable] 10 | #endif 11 | public class Item : IPr0grammItem 12 | { 13 | public int Id { get; set; } 14 | [JsonProperty(PropertyName = "promoted")] 15 | public int PromotedId { get; set; } 16 | [JsonProperty(PropertyName = "up")] 17 | public int Upvotes { get; set; } 18 | [JsonProperty(PropertyName = "down")] 19 | public int Downvotes { get; set; } 20 | public int Width { get; set; } 21 | public int Height { get; set; } 22 | [JsonProperty(PropertyName = "created")] 23 | [JsonConverter(typeof(UnixDateTimeConverter))] 24 | public DateTime CreatedAt { get; set; } 25 | /// Use the BaseAddress property of your HttpClient to prepend the protocol and host name. 26 | [JsonProperty(PropertyName = "image")] 27 | public string ImageUrl { get; set; } 28 | /// Use the BaseAddress property of your HttpClient to prepend the protocol and host name. 29 | [JsonProperty(PropertyName = "thumb")] 30 | public string ThumbnailUrl { get; set; } 31 | /// Use the BaseAddress property of your HttpClient to prepend the protocol and host name. 32 | [JsonProperty(PropertyName = "fullsize")] 33 | public string FullSizeUrl { get; set; } 34 | public string Source { get; set; } 35 | public ItemFlags Flags { get; set; } 36 | public string User { get; set; } 37 | public UserMark Mark { get; set; } 38 | public bool Audio { get; set; } 39 | 40 | public ItemType GetItemType() 41 | { 42 | var url = ImageUrl; 43 | if (string.IsNullOrWhiteSpace(url)) 44 | return ItemType.Unknown; 45 | return url.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) ? ItemType.Video : ItemType.Image; 46 | } 47 | 48 | public string GetAbsoluteThumbnailUrl(bool secure) => ClientConstants.GetThumbnailUrlPrefix(secure) + "/" + ThumbnailUrl; 49 | public string GetAbsoluteFullSizeUrl(bool secure) => FullSizeUrl == null ? GetAbsoluteImageUrl(secure) : ClientConstants.GetFullSizeUrlPrefix(secure) + "/" + FullSizeUrl; 50 | public string GetAbsoluteImageUrl(bool secure) => ClientConstants.GetImageUrlPrefix(secure) + "/" + ImageUrl; 51 | } 52 | 53 | public enum ItemType 54 | { 55 | Unknown, // TODO consider 56 | Image, 57 | Video 58 | } 59 | 60 | public interface IPr0grammItem 61 | { 62 | int Id { get; } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Service/IPr0grammUserService.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | using OpenPr0gramm.Endpoint.User.Response; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | public interface IPr0grammUserService 8 | { 9 | [Post("/user/ban")] 10 | Task Ban([Body(BodySerializationMethod.UrlEncoded)]BanData data); 11 | 12 | [Post("/user/changeemail")] 13 | Task ChangeEmail([Body(BodySerializationMethod.UrlEncoded)]ChangeEmailData data); 14 | 15 | [Post("/user/changepassword")] 16 | Task ChangePassword([Body(BodySerializationMethod.UrlEncoded)]ChangePasswordData data); 17 | 18 | [Get("/user/followlist")] 19 | Task GetFollowList(ItemFlags flags); 20 | 21 | [Get("/user/info")] 22 | Task GetInfo(); 23 | 24 | [Get("/user/name")] 25 | Task GetName(); 26 | 27 | [Post("/user/invite")] 28 | Task Invite([Body(BodySerializationMethod.UrlEncoded)]InviteData data); 29 | 30 | [Post("/user/joinwithinvite")] 31 | Task JoinWithInvite([Body(BodySerializationMethod.UrlEncoded)]JoinWithTokenData data); 32 | 33 | [Post("/user/joinwithtoken")] 34 | Task JoinWithToken([Body(BodySerializationMethod.UrlEncoded)]JoinWithTokenData data); 35 | 36 | [Get("/user/loadinvite")] 37 | Task LoadInvite(string token); 38 | 39 | [Post("/user/loadpaymenttoken")] 40 | Task LoadPaymentToken([Body(BodySerializationMethod.UrlEncoded)]TokenActionData data); 41 | 42 | [Get("/user/loggedin")] 43 | Task LoggedIn(); 44 | 45 | [Post("/user/login")] 46 | Task LogIn([Body(BodySerializationMethod.UrlEncoded)]LogInData data); 47 | 48 | [Post("/user/logout")] 49 | Task LogOut([Body(BodySerializationMethod.UrlEncoded)]LogOutData data); 50 | 51 | [Post("/user/captcha")] 52 | Task RequestCaptcha(); 53 | 54 | [Post("/user/redeemtoken")] 55 | Task RedeemToken([Body(BodySerializationMethod.UrlEncoded)]TokenActionData data); 56 | 57 | [Post("/user/requestemailchange")] 58 | Task RequestEmailChange([Body(BodySerializationMethod.UrlEncoded)]RequestEmailChangeData data); 59 | 60 | [Post("/user/resetpassword")] 61 | Task ResetPassword([Body(BodySerializationMethod.UrlEncoded)]ResetPasswordData data); 62 | 63 | [Post("/user/sendpasswordresetmail")] 64 | Task SendPasswordResetMail([Body(BodySerializationMethod.UrlEncoded)]SendPasswordResetMailData data); 65 | 66 | [Post("/user/sitesettings")] 67 | Task SetSiteSettings([Body(BodySerializationMethod.UrlEncoded)]SiteSettingsData data); 68 | 69 | [Get("/user/sync")] 70 | Task Sync(int offset); 71 | 72 | [Post("/user/validate")] 73 | Task Validate([Body(BodySerializationMethod.UrlEncoded)]TokenActionData data); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/User.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using OpenPr0gramm.Json; 3 | using System; 4 | 5 | namespace OpenPr0gramm 6 | { 7 | #if FW 8 | [Serializable] 9 | #endif 10 | public class CommentUser : IPr0grammUser 11 | { 12 | public int Id { get; set; } 13 | public string Name { get; set; } 14 | public UserMark Mark { get; set; } 15 | 16 | public override string ToString() => $"{Name} ({Id}; {Mark})"; 17 | } 18 | 19 | #if FW 20 | [Serializable] 21 | #endif 22 | public class User : CommentUser 23 | { 24 | [JsonProperty(PropertyName = "registered")] 25 | [JsonConverter(typeof(UnixDateTimeConverter))] 26 | public DateTime RegisteredSince { get; set; } 27 | public int Score { get; set; } 28 | [JsonProperty(PropertyName = "admin")] 29 | public bool IsAdmin { get; set; } 30 | [JsonProperty(PropertyName = "banned")] 31 | public bool IsBanned { get; set; } 32 | [JsonProperty(PropertyName = "bannedUntil")] 33 | [JsonConverter(typeof(UnixDateTimeConverter))] 34 | public DateTime? BannedUntil { get; set; } 35 | [JsonProperty(PropertyName = "itemDelete")] 36 | public long ItemDelete { get; set; } 37 | [JsonProperty(PropertyName = "commentDelete")] 38 | public long CommentDelete { get; set; } 39 | 40 | public override string ToString() => $"{base.ToString()} (RegisteredSince: {RegisteredSince}, Score: {Score}, IsAdmin: {IsAdmin}, IsBanned: {IsBanned}, itemDelete: {ItemDelete}, commentDelete: {CommentDelete})"; 41 | } 42 | 43 | #if FW 44 | [Serializable] 45 | #endif 46 | public class FollowedUser : INamedPr0grammUser 47 | { 48 | public string ItemId { get; set; } 49 | /// Use the BaseAddress property of your HttpClient to prepend the protocol and host name. 50 | [JsonProperty(PropertyName = "thumb")] 51 | public string ThumbnailUrl { get; set; } 52 | public string Name { get; set; } 53 | public UserMark Mark { get; set; } 54 | [JsonProperty(PropertyName = "lastPost")] 55 | [JsonConverter(typeof(UnixDateTimeConverter))] 56 | public DateTime LastPostAt { get; set; } 57 | [JsonProperty(PropertyName = "followCreated")] 58 | [JsonConverter(typeof(UnixDateTimeConverter))] 59 | public DateTime FollowingSince { get; set; } 60 | } 61 | 62 | #if FW 63 | [Serializable] 64 | #endif 65 | public class InvitedUser : INamedPr0grammUser 66 | { 67 | public string Name { get; set; } 68 | public UserMark Mark { get; set; } 69 | public string Email { get; set; } 70 | [JsonProperty(PropertyName = "created")] 71 | [JsonConverter(typeof(UnixDateTimeConverter))] 72 | public DateTime RegisteredAt { get; set; } 73 | } 74 | 75 | #if FW 76 | [Serializable] 77 | #endif 78 | public class InvitingUser : INamedPr0grammUser 79 | { 80 | public string Name { get; set; } 81 | public UserMark Mark { get; set; } 82 | public string Email { get; set; } 83 | } 84 | 85 | #if FW 86 | [Serializable] 87 | #endif 88 | public class MarkedUser : INamedPr0grammUser 89 | { 90 | public string Name { get; set; } 91 | public UserMark Mark { get; set; } 92 | } 93 | 94 | public interface INamedPr0grammUser 95 | { 96 | string Name { get; } 97 | } 98 | 99 | public interface IPr0grammUser : INamedPr0grammUser 100 | { 101 | int Id { get; } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archiviert am 2025-03-29 2 | benutz was anderes 3 | 4 | # OpenPr0gramm [![Travis Build Status](https://travis-ci.com/holzmaster/OpenPr0gramm.svg?branch=master)](https://travis-ci.com/holzmaster/OpenPr0gramm) [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/uckh08ose23rap7l?svg=true)](https://ci.appveyor.com/project/holzmaster/openpr0gramm) ![NuGet Version](https://img.shields.io/nuget/v/OpenPr0gramm.svg) [![LGPL-3.0 License](https://img.shields.io/github/license/holzmaster/OpenPr0gramm.svg)](https://github.com/holzmaster/OpenPr0gramm/blob/master/LICENSE) 5 | Eine quelloffene .NET-Implementierung für das pr0gramm. 6 | 7 | ## Installation 8 | 9 | Via NuGet: 10 | ``` 11 | Install-Package OpenPr0gramm 12 | ``` 13 | 14 | **Achtung:** Nach dem Installieren wird eine `RefitStubs.cs` zu dem Projekt hinzugefügt. Diese wird immer beim Kompilieren neu generiert. **Du darfst sie nicht löschen**, sonst findet der Generator nicht mehr und wirft einen Compilerfehler. Ich weiß, du brauchst sie eigentlich nicht, aber aufgrund einer bescheuerten Designentscheidung von Refit (siehe [die Issue dazu](https://github.com/paulcbetts/refit/issues/120)) geht das nicht anders. Wenn du gerade eine freie Minute hast, schau doch bei der [Issue](https://github.com/paulcbetts/refit/issues/120) vorbei und weise den Maintainer darauf hin, dass man da was machen sollte. 15 | 16 | ## Verwendung 17 | Die Library besteht aus 3 Schichten und ist an der JS-API der Webseite orientiert: 18 | 19 | 1. Refit-Interface-HTTP-Wrapper 20 | 2. Mapping der Rohdaten auf die Interface-Abstraktionen (`IPr0grammApiClient`) 21 | 3. Wrapping von abstrahierten Parametern auf die Rohdaten (`Pr0grammClient`) 22 | 23 | Für den normalen Umgang sollte der 3. Layer reichen. Wenn du willst, kannst du aber auch die einzelnen Schichten austauschen. Die Library sollte auf allen Plattformen lauffähig sein, auf denen Refit und JSON.NET funktionieren. 24 | 25 | Hier etwas Beispielcode: 26 | ```C# 27 | var client = new Pr0grammClient(); 28 | 29 | var captchaRes = await client.User.RequestCaptcha(); 30 | var captchaImage = captchaRes.GetCaptchaImageBytes(); 31 | var loginRes = await client.User.LogIn("user", "password", captchaRes.Token, "aaaaa"); 32 | if(!loginRes.Success) 33 | { 34 | if(loginRes.Ban != null && loginRes.Ban.IsBanned) 35 | { 36 | Console.WriteLine($"Du bist bis {loginRes.Ban.Until} gebannt. Warum? \"{loginRes.Ban.Reason}\"."); 37 | } 38 | else 39 | { 40 | Console.WriteLine("Das Passwort war wohl falsch oder so."); 41 | } 42 | return; 43 | } 44 | 45 | var frontItemRes = await client.Item.GetItems(ItemFlags.SFW, ItemStatus.Promoted); 46 | Console.WriteLine("Posts:"); 47 | foreach(var item in frontItemRes.Items) 48 | { 49 | Console.WriteLine($"{item.Id} von {item.User} ({item.Mark})"); 50 | } 51 | 52 | CookieContainer container = client.GetCookie(); // Kann weggespeichert/serialisiert werden 53 | // für spätere Verwendung (um sich nicht noch mal einloggen zu müssen) 54 | var client2 = new Pr0grammClient(container); // Client mit Satz an Cookies initialisieren 55 | ``` 56 | Der Rest sollte selbsterklärend sein. Sämtliche Funktionalität befindet sich bei der `Pr0grammClient`-Klasse. 57 | 58 | ## Nutzungsbestimmungen/Lizenz 59 | Zusätzlich zu den in der LICENSE-File angegebenen Bestimmungen gilt: 60 | - **Keine kommerzielle Nutzung.** 61 | - Hole dir *vorher* die Erlaubnis der Seitenbetreiber, deine Anwendung zu entwickeln. 62 | Wenn du etwas vorhast, was nicht in Einklang mit den Nutzungsbestimmungen ist, kontaktiere mich (via pr0gramm/Email) und wir können drüber reden. 63 | 64 | ## Bugs 65 | Es kann sein, dass bei der Serialisierung bestimmte Felder aufgrund von Typos oder Brainlags bei der Implementierung nicht richtig gemappt werden. Wenn dir sowas auffällt, kontaktiere mich bitte, poste eine Issue, oder fix es selbst und stelle einen Pull-Request. 66 | -------------------------------------------------------------------------------- /OpenPr0gramm.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2024 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenPr0gramm", "src\OpenPr0gramm\OpenPr0gramm.csproj", "{61FBF235-F36B-46CD-8E1F-8142AA001355}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenPr0gramm.Tests", "src\OpenPr0gramm.Tests\OpenPr0gramm.Tests.csproj", "{DD756BA6-655B-4106-955F-40079EC089D3}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|ARM = Debug|ARM 14 | Debug|x64 = Debug|x64 15 | Debug|x86 = Debug|x86 16 | Release|Any CPU = Release|Any CPU 17 | Release|ARM = Release|ARM 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Debug|ARM.ActiveCfg = Debug|Any CPU 25 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Debug|ARM.Build.0 = Debug|Any CPU 26 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Debug|x64.ActiveCfg = Debug|Any CPU 27 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Debug|x64.Build.0 = Debug|Any CPU 28 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Debug|x86.Build.0 = Debug|Any CPU 30 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Release|ARM.ActiveCfg = Release|Any CPU 33 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Release|ARM.Build.0 = Release|Any CPU 34 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Release|x64.ActiveCfg = Release|Any CPU 35 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Release|x64.Build.0 = Release|Any CPU 36 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Release|x86.ActiveCfg = Release|Any CPU 37 | {61FBF235-F36B-46CD-8E1F-8142AA001355}.Release|x86.Build.0 = Release|Any CPU 38 | {DD756BA6-655B-4106-955F-40079EC089D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {DD756BA6-655B-4106-955F-40079EC089D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {DD756BA6-655B-4106-955F-40079EC089D3}.Debug|ARM.ActiveCfg = Debug|Any CPU 41 | {DD756BA6-655B-4106-955F-40079EC089D3}.Debug|ARM.Build.0 = Debug|Any CPU 42 | {DD756BA6-655B-4106-955F-40079EC089D3}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {DD756BA6-655B-4106-955F-40079EC089D3}.Debug|x64.Build.0 = Debug|Any CPU 44 | {DD756BA6-655B-4106-955F-40079EC089D3}.Debug|x86.ActiveCfg = Debug|Any CPU 45 | {DD756BA6-655B-4106-955F-40079EC089D3}.Debug|x86.Build.0 = Debug|Any CPU 46 | {DD756BA6-655B-4106-955F-40079EC089D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {DD756BA6-655B-4106-955F-40079EC089D3}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {DD756BA6-655B-4106-955F-40079EC089D3}.Release|ARM.ActiveCfg = Release|Any CPU 49 | {DD756BA6-655B-4106-955F-40079EC089D3}.Release|ARM.Build.0 = Release|Any CPU 50 | {DD756BA6-655B-4106-955F-40079EC089D3}.Release|x64.ActiveCfg = Release|Any CPU 51 | {DD756BA6-655B-4106-955F-40079EC089D3}.Release|x64.Build.0 = Release|Any CPU 52 | {DD756BA6-655B-4106-955F-40079EC089D3}.Release|x86.ActiveCfg = Release|Any CPU 53 | {DD756BA6-655B-4106-955F-40079EC089D3}.Release|x86.Build.0 = Release|Any CPU 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(ExtensibilityGlobals) = postSolution 59 | SolutionGuid = {B9701346-4B02-46E1-9908-7055652277A4} 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | RefitStubs.cs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | build/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | 88 | # TFS 2012 Local Workspace 89 | $tf/ 90 | 91 | # Guidance Automation Toolkit 92 | *.gpState 93 | 94 | # ReSharper is a .NET coding add-in 95 | _ReSharper*/ 96 | *.[Rr]e[Ss]harper 97 | *.DotSettings.user 98 | 99 | # JustCode is a .NET coding add-in 100 | .JustCode 101 | 102 | # TeamCity is a build add-in 103 | _TeamCity* 104 | 105 | # DotCover is a Code Coverage Tool 106 | *.dotCover 107 | 108 | # NCrunch 109 | _NCrunch_* 110 | .*crunch*.local.xml 111 | 112 | # MightyMoose 113 | *.mm.* 114 | AutoTest.Net/ 115 | 116 | # Web workbench (sass) 117 | .sass-cache/ 118 | 119 | # Installshield output folder 120 | [Ee]xpress/ 121 | 122 | # DocProject is a documentation generator add-in 123 | DocProject/buildhelp/ 124 | DocProject/Help/*.HxT 125 | DocProject/Help/*.HxC 126 | DocProject/Help/*.hhc 127 | DocProject/Help/*.hhk 128 | DocProject/Help/*.hhp 129 | DocProject/Help/Html2 130 | DocProject/Help/html 131 | 132 | # Click-Once directory 133 | publish/ 134 | 135 | # Publish Web Output 136 | *.[Pp]ublish.xml 137 | *.azurePubxml 138 | # TODO: Comment the next line if you want to checkin your web deploy settings 139 | # but database connection strings (with potential passwords) will be unencrypted 140 | *.pubxml 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/ItemDownloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace OpenPr0gramm 7 | { 8 | // TODO test 9 | public class ItemDownloader : IDisposable 10 | { 11 | private static readonly string UserAgent = ClientConstants.GetUserAgent(nameof(ItemDownloader)); 12 | 13 | public DownloadKind DownloadKind { get; set; } 14 | public bool UsingHttps { get; } 15 | 16 | public HttpClient HttpClient { get; } 17 | 18 | #region Ctors 19 | public ItemDownloader(DownloadKind downloadKind) 20 | : this(downloadKind, true) 21 | { } 22 | 23 | public ItemDownloader(DownloadKind downloadKind, bool useHttps) 24 | { 25 | DownloadKind = downloadKind; 26 | UsingHttps = useHttps; 27 | HttpClient = CreateHttpClient(downloadKind, useHttps); 28 | } 29 | 30 | #endregion 31 | 32 | public Task DownloadItemAsync(Item item) 33 | { 34 | if (item == null) 35 | throw new ArgumentNullException(nameof(item)); 36 | 37 | // save class members to locals since they can change during method execution 38 | var kind = DownloadKind; 39 | 40 | if (!Enum.IsDefined(typeof(DownloadKind), kind)) // TODO consider remove since all code paths throw exceptions anyways 41 | throw new InvalidOperationException(); 42 | 43 | // if it's a thumbnail, easy going. 44 | if (kind == DownloadKind.Thumbnail) 45 | return HttpClient.GetStreamAsync(item.ThumbnailUrl); 46 | 47 | // if not, consider webm/mpeg and stuff. 48 | var type = item.GetItemType(); 49 | switch (type) 50 | { 51 | case ItemType.Image: 52 | // consider requested quality here 53 | switch (kind) // cannot be DownloadKind.Thumbnail 54 | { 55 | case DownloadKind.NormalImage: 56 | return HttpClient.GetStreamAsync(item.ImageUrl); 57 | case DownloadKind.LargestAvailable: 58 | var bestUrl = string.IsNullOrWhiteSpace(item.FullSizeUrl) ? item.ImageUrl : item.FullSizeUrl; 59 | return HttpClient.GetStreamAsync(bestUrl); 60 | default: 61 | throw new InvalidOperationException(); 62 | } 63 | case ItemType.Video: 64 | // pr0gramm used to offer webms and mpegs. It seems that they only ave MP4. 65 | return HttpClient.GetStreamAsync(item.ImageUrl); // webm urls are always in the "image" field 66 | default: 67 | throw new InvalidOperationException(); 68 | } 69 | } 70 | 71 | private static IPr0grammItemsService GetServiceFromClient(IPr0grammApiClient client) 72 | { 73 | if (client == null) 74 | throw new ArgumentNullException(nameof(client)); 75 | return client.Items; 76 | } 77 | private static HttpClient CreateHttpClient(DownloadKind downloadKind, bool useHttps) 78 | { 79 | var client = new HttpClient 80 | { 81 | BaseAddress = new Uri(GetBaseAddressForDownloadKind(downloadKind, useHttps)) 82 | }; 83 | client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent); 84 | return client; 85 | } 86 | private static string GetBaseAddressForDownloadKind(DownloadKind downloadKind, bool useHttps) 87 | { 88 | switch (downloadKind) 89 | { 90 | case DownloadKind.Thumbnail: 91 | return ClientConstants.GetThumbnailUrlPrefix(useHttps); 92 | case DownloadKind.NormalImage: 93 | return ClientConstants.GetImageUrlPrefix(useHttps); 94 | case DownloadKind.LargestAvailable: 95 | return ClientConstants.GetFullSizeUrlPrefix(useHttps); 96 | default: 97 | throw new InvalidOperationException(); 98 | } 99 | } 100 | 101 | public void Dispose() => HttpClient.Dispose(); 102 | } 103 | 104 | public enum DownloadKind 105 | { 106 | Thumbnail, 107 | NormalImage, 108 | LargestAvailable 109 | // TODO consider Source 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/ProfileBadge.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using OpenPr0gramm.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace OpenPr0gramm 8 | { 9 | public class ProfileBadge 10 | { 11 | public string Image { get; set; } 12 | public string Description { get; set; } 13 | public string Link { get; set; } 14 | [JsonProperty(PropertyName = "created")] 15 | [JsonConverter(typeof(UnixDateTimeConverter))] 16 | public DateTime CreatedAt { get; set; } 17 | 18 | public ProfileBadge() 19 | { } 20 | public ProfileBadge(string image, string description, string link, DateTime createdAt) 21 | : this() 22 | { 23 | Image = image; 24 | Description = description; 25 | Link = link; 26 | CreatedAt = createdAt; 27 | } 28 | 29 | public override string ToString() => Description; 30 | } 31 | 32 | public class DynamicProfileBadge : ProfileBadge 33 | { 34 | public string Name { get; set; } 35 | public string Extra { get; set; } 36 | public DynamicProfileBadge(string image, string description, string link, DateTime createdAt, string name, string extra) 37 | : base(image, description, link, createdAt) 38 | { 39 | Name = name; 40 | Extra = extra; 41 | } 42 | 43 | public static DynamicProfileBadge CreateFromCommentCount(string userName, int commentCount, DateTime newestCommentTime) 44 | { 45 | if (string.IsNullOrWhiteSpace(userName)) 46 | throw new ArgumentNullException(nameof(userName)); 47 | foreach (var ccn in CommentCountNames.OrderByDescending(kvp => kvp.Key)) 48 | { 49 | if (commentCount > ccn.Key) 50 | { 51 | var template = BadgeTemplates[DynamicBadgeType.Comments]; 52 | var description = string.Format(template.Description ?? string.Empty, ccn.Key); 53 | var link = $"{ClientConstants.UserUrlPrefix}/{userName}/comments/before/{newestCommentTime.ToUnixTime()}"; 54 | return new DynamicProfileBadge(template.Image, description, link, newestCommentTime, template.Name, ccn.Value); 55 | } 56 | } 57 | return null; 58 | } 59 | 60 | public static DynamicProfileBadge CreateFromRegistrationDate(string userName, DateTime registeredSince) 61 | { 62 | if (string.IsNullOrWhiteSpace(userName)) 63 | throw new ArgumentNullException(nameof(userName)); 64 | var diffYears = (DateTime.Now - registeredSince).Days / 365; 65 | if (diffYears <= 0) 66 | return null; 67 | var template = BadgeTemplates[DynamicBadgeType.Years]; 68 | 69 | var description = string.Format(template.Description ?? string.Empty, diffYears, diffYears != 1 ? "e" : string.Empty); 70 | var link = $"{ClientConstants.UserUrlPrefix}/{userName}"; 71 | return new DynamicProfileBadge(template.Image, description, link, registeredSince, template.Name, diffYears.ToString()); 72 | } 73 | 74 | private static Dictionary BadgeTemplates = new Dictionary 75 | { 76 | [DynamicBadgeType.Comments] = new BadgeTemplate("comments", "Hat mehr als {0} Kommentare verfasst", null, null, ClientConstants.BadgeUrlPrefix + "/comments.png"), 77 | [DynamicBadgeType.Years] = new BadgeTemplate("years", "Hat {0} Jahr{1} auf pr0gramm verschwendet", null, null, ClientConstants.BadgeUrlPrefix + "/years.png") 78 | }; 79 | private static readonly Dictionary CommentCountNames = new Dictionary 80 | { 81 | [1000] = "1k", 82 | [5000] = "5k", 83 | [10000] = "10k" 84 | }; 85 | } 86 | 87 | internal struct BadgeTemplate 88 | { 89 | public string Name { get; } 90 | public string Description { get; } 91 | public string Link { get; } 92 | public string Extra { get; } 93 | public string Image { get; } 94 | public BadgeTemplate(string name, string description, string link, string extra, string image) 95 | { 96 | Name = name; 97 | Description = description; 98 | Link = link; 99 | Extra = extra; 100 | Image = image; 101 | } 102 | } 103 | internal enum DynamicBadgeType 104 | { 105 | Comments = 0, 106 | Years = 1 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/OpenPr0gramm.Tests/ItemDownloaderTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | namespace OpenPr0gramm.Tests 8 | { 9 | [TestFixture] 10 | public class ItemDownloaderTests 11 | { 12 | private IReadOnlyList _items; 13 | 14 | [OneTimeSetUp] 15 | public async Task GetItems() 16 | { 17 | var cl = new Pr0grammClient(); 18 | var res = await cl.Item.GetItemsOlder( 19 | ItemFlags.SFW, 20 | ItemStatus.New, 21 | false, 22 | null, 23 | null, 24 | null, 25 | false, 26 | 1196866 27 | ).ConfigureAwait(false); 28 | 29 | Assert.NotNull(res); 30 | Assert.That(res.Items, Is.Not.Empty); 31 | Assert.AreEqual(120, res.Items.Count); 32 | CollectionAssert.AllItemsAreNotNull(res.Items); 33 | CollectionAssert.AllItemsAreUnique(res.Items); 34 | 35 | _items = res.Items; 36 | 37 | // foreach (var item in _items) 38 | // System.Console.Error.WriteLine(item.ImageUrl); 39 | 40 | Assert.AreEqual(_items[0].Id, 1196864); 41 | Assert.That(_items[0].ImageUrl, Does.EndWith(".jpg")); 42 | 43 | Assert.AreEqual(_items[1].Id, 1196863); 44 | Assert.That(_items[1].ImageUrl, Does.EndWith(".mp4")); 45 | 46 | Assert.AreEqual(_items[2].Id, 1196862); 47 | Assert.That(_items[2].ImageUrl, Does.EndWith(".jpg")); 48 | 49 | Assert.AreEqual(_items[10].Id, 1196850); 50 | Assert.That(_items[10].ImageUrl, Is.Not.Null.And.Not.Empty); 51 | } 52 | 53 | [Test] 54 | public async Task ThumbnailTest() 55 | { 56 | const string expected = "http://thumb.pr0gramm.com/2016/02/25/4bb72760225fafcf.jpg"; 57 | 58 | var item = _items[0]; 59 | 60 | var expectedFile = await DownloadExpectedFile(expected); 61 | 62 | using var dl = new ItemDownloader(DownloadKind.Thumbnail, true); 63 | var actualStream = await dl.DownloadItemAsync(item); 64 | var actualFile = await StreamToFile(actualStream); 65 | 66 | CompareFiles(expectedFile, actualFile); 67 | } 68 | 69 | [Test] 70 | public async Task ImageTest() 71 | { 72 | const string expected = "http://img.pr0gramm.com/2016/02/25/4bb72760225fafcf.jpg"; 73 | 74 | var item = _items[0]; 75 | 76 | var expectedFile = await DownloadExpectedFile(expected); 77 | 78 | using var dl = new ItemDownloader(DownloadKind.NormalImage, true); 79 | var actualStream = await dl.DownloadItemAsync(item); 80 | var actualFile = await StreamToFile(actualStream); 81 | 82 | CompareFiles(expectedFile, actualFile); 83 | } 84 | 85 | [Test] 86 | public async Task FullSizeTest() 87 | { 88 | const string expected = "http://full.pr0gramm.com/2016/02/25/70e033c12769af8c.jpg"; 89 | 90 | var item = _items[3]; 91 | 92 | var expectedFile = await DownloadExpectedFile(expected); 93 | 94 | using var dl = new ItemDownloader(DownloadKind.LargestAvailable, true); 95 | var actualStream = await dl.DownloadItemAsync(item); 96 | var actualFile = await StreamToFile(actualStream); 97 | 98 | CompareFiles(expectedFile, actualFile); 99 | } 100 | 101 | 102 | [Test] 103 | public async Task Mp4Test() 104 | { 105 | const string expected = "http://img.pr0gramm.com/2016/02/25/956743d57e310de0.mp4"; 106 | 107 | var item = _items[1]; 108 | Assert.NotNull(item); 109 | 110 | var expectedFile = await DownloadExpectedFile(expected); 111 | 112 | using var dl = new ItemDownloader(DownloadKind.NormalImage, true); 113 | var actualStream = await dl.DownloadItemAsync(item); 114 | var actualFile = await StreamToFile(actualStream); 115 | 116 | CompareFiles(expectedFile, actualFile); 117 | } 118 | 119 | private static async Task DownloadFile(string expectedFile, string url) 120 | { 121 | using var expectedStream = File.OpenWrite(expectedFile); 122 | using var cl = new HttpClient(); 123 | cl.DefaultRequestHeaders.UserAgent.ParseAdd("OpenPr0gramm/1.0 (ApiTests)"); 124 | 125 | var stream = await cl.GetStreamAsync(url).ConfigureAwait(false); 126 | await stream.CopyToAsync(expectedStream).ConfigureAwait(false); 127 | } 128 | private static async Task DownloadExpectedFile(string url) 129 | { 130 | var name = Path.GetTempFileName(); 131 | await DownloadFile(name, url).ConfigureAwait(false); 132 | return name; 133 | } 134 | private static async Task StreamToFile(Stream source) 135 | { 136 | var res = Path.GetTempFileName(); 137 | using (var str = File.OpenWrite(res)) 138 | await source.CopyToAsync(str).ConfigureAwait(false); 139 | return res; 140 | } 141 | 142 | private static void CompareFiles(string expected, string actual) 143 | { 144 | var expectedBytes = File.ReadAllBytes(expected); 145 | var actualBytes = File.ReadAllBytes(actual); 146 | CollectionAssert.AreEqual(expectedBytes, actualBytes); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Pr0grammApiClient.cs: -------------------------------------------------------------------------------- 1 | using JsonNet.PrivateSettersContractResolvers; 2 | using Newtonsoft.Json; 3 | using Refit; 4 | using System; 5 | using System.Net; 6 | using System.Net.Http; 7 | using OpenPr0gramm.Endpoint.Inbox; 8 | 9 | namespace OpenPr0gramm 10 | { 11 | public class Pr0grammApiClient : IPr0grammApiClient 12 | { 13 | private readonly HttpClient _client; 14 | private readonly HttpClientHandler _clientHandler; 15 | private static readonly string UserAgent = ClientConstants.GetUserAgent(nameof(Pr0grammApiClient)); 16 | 17 | public IPr0grammUserService User { get; } 18 | public IPr0grammTagsService Tags { get; } 19 | public IPr0grammProfileService Profile { get; } 20 | public IPr0grammItemsService Items { get; } 21 | public IPr0grammInboxService Inbox { get; } 22 | public IPr0grammCommentsService Comments { get; } 23 | public IPr0grammPaypalService Paypal { get; } 24 | public IPr0grammContactService Contact { get; } 25 | public IPr0grammBitcoinService Bitcoin { get; } 26 | 27 | private static RefitSettings _refitSettings = new RefitSettings 28 | { 29 | UrlParameterFormatter = new EnumsAsIntegersParameterFormatter(), 30 | JsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new PrivateSetterCamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore } 31 | }; 32 | 33 | public Pr0grammApiClient() 34 | : this(null) 35 | { } 36 | public Pr0grammApiClient(CookieContainer cookieContainer) 37 | { 38 | _clientHandler = new HttpClientHandler 39 | { 40 | CookieContainer = cookieContainer ?? new CookieContainer() 41 | }; 42 | 43 | _client = new HttpClient(_clientHandler) { BaseAddress = new Uri(ClientConstants.ApiBaseUrl) }; 44 | _client.DefaultRequestVersion = HttpVersion.Version20; 45 | _client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent); 46 | 47 | User = RestService.For(_client, _refitSettings); // Done 48 | Tags = RestService.For(_client, _refitSettings); // Done 49 | Profile = RestService.For(_client, _refitSettings); // Done 50 | Items = RestService.For(_client, _refitSettings); // Done 51 | Inbox = RestService.For(_client, _refitSettings); // Done 52 | Comments = RestService.For(_client, _refitSettings); // Done 53 | Paypal = RestService.For(_client, _refitSettings); // Done 54 | Contact = RestService.For(_client, _refitSettings); // Done 55 | Bitcoin = RestService.For(_client, _refitSettings); // Done 56 | } 57 | 58 | public CookieContainer GetCookies() => _clientHandler?.CookieContainer; 59 | 60 | public string GetCurrentNonce() 61 | { 62 | var sessionId = GetCurrentSessionId(); 63 | return sessionId?.Substring(0, 16); 64 | } 65 | 66 | public string GetCurrentSessionId() 67 | { 68 | return GetMeCookie()?.Id; 69 | } 70 | 71 | private Pr0grammMeCookie GetMeCookie() 72 | { 73 | var container = _clientHandler?.CookieContainer; 74 | if (container == null) 75 | return null; 76 | var cookies = container.GetCookies(new Uri(ClientConstants.ProtocolPrefix + ClientConstants.HostName + "/")); 77 | var meCookie = cookies["me"]?.Value; 78 | if (meCookie == null) 79 | return null; 80 | meCookie = WebUtility.UrlDecode(meCookie); 81 | return JsonConvert.DeserializeObject(meCookie); 82 | } 83 | 84 | #region IDisposable Support 85 | 86 | private bool disposedValue = false; // To detect redundant calls 87 | 88 | protected virtual void Dispose(bool disposing) 89 | { 90 | if (!disposedValue) 91 | { 92 | if (disposing) 93 | { 94 | _clientHandler.Dispose(); 95 | _client.Dispose(); 96 | } 97 | disposedValue = true; 98 | } 99 | } 100 | 101 | public void Dispose() 102 | { 103 | Dispose(true); 104 | GC.SuppressFinalize(this); 105 | } 106 | 107 | #endregion 108 | } 109 | 110 | internal class Pr0grammMeCookie 111 | { 112 | [JsonProperty("n")] 113 | public string N { get; set; } 114 | [JsonProperty("id")] 115 | public string Id { get; set; } 116 | [JsonProperty("a")] 117 | public bool A { get; set; } 118 | [JsonProperty("pp")] 119 | public string pp { get; set; } 120 | [JsonProperty("paid")] 121 | public bool Paid { get; set; } 122 | [JsonProperty("verified")] 123 | public bool Verified { get; set; } 124 | } 125 | 126 | public interface IPr0grammApiClient : IDisposable 127 | { 128 | IPr0grammUserService User { get; } 129 | IPr0grammTagsService Tags { get; } 130 | IPr0grammProfileService Profile { get; } 131 | IPr0grammItemsService Items { get; } 132 | IPr0grammInboxService Inbox { get; } 133 | IPr0grammCommentsService Comments { get; } 134 | IPr0grammPaypalService Paypal { get; } 135 | IPr0grammContactService Contact { get; } 136 | IPr0grammBitcoinService Bitcoin { get; } 137 | 138 | CookieContainer GetCookies(); 139 | string GetCurrentNonce(); 140 | string GetCurrentSessionId(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /src/OpenPr0gramm/Pr0grammClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Threading.Tasks; 7 | using OpenPr0gramm.Endpoint.Inbox.FormData; 8 | using OpenPr0gramm.Endpoint.Inbox.Response; 9 | 10 | namespace OpenPr0gramm 11 | { 12 | public class Pr0grammClient : IDisposable 13 | { 14 | private readonly IPr0grammApiClient _client; 15 | 16 | public BitcoinController Bitcoin { get; } 17 | public CommentController Comment { get; } 18 | public InboxController Inbox { get; } 19 | public ContactController Contact { get; } 20 | public ProfileController Profile { get; } 21 | public PaypalController Paypal { get; } 22 | public UserController User { get; } 23 | public TagController Tag { get; } 24 | public ItemController Item { get; } 25 | 26 | public Pr0grammClient() 27 | : this(new Pr0grammApiClient()) 28 | { } 29 | public Pr0grammClient(CookieContainer cookieContainer) 30 | : this(new Pr0grammApiClient(cookieContainer)) 31 | { } 32 | public Pr0grammClient(IPr0grammApiClient apiClient) 33 | { 34 | if (apiClient == null) 35 | throw new ArgumentNullException(nameof(apiClient)); 36 | _client = apiClient; 37 | Bitcoin = new BitcoinController(_client); 38 | Comment = new CommentController(_client); 39 | Inbox = new InboxController(_client); 40 | Contact = new ContactController(_client); 41 | Profile = new ProfileController(_client); 42 | Paypal = new PaypalController(_client); 43 | User = new UserController(_client); 44 | Tag = new TagController(_client); 45 | Item = new ItemController(_client); 46 | } 47 | 48 | public CookieContainer GetCookies() => _client.GetCookies(); 49 | 50 | #region IDisposable Support 51 | 52 | private bool disposedValue = false; // To detect redundant calls 53 | 54 | protected virtual void Dispose(bool disposing) 55 | { 56 | if (!disposedValue) 57 | { 58 | if (disposing) 59 | { 60 | _client.Dispose(); 61 | } 62 | disposedValue = true; 63 | } 64 | } 65 | 66 | public void Dispose() 67 | { 68 | Dispose(true); 69 | GC.SuppressFinalize(this); 70 | } 71 | 72 | #endregion 73 | } 74 | 75 | public abstract class Pr0grammController 76 | { 77 | protected readonly IPr0grammApiClient Client; 78 | protected Pr0grammController(IPr0grammApiClient client) 79 | { 80 | Client = client; 81 | } 82 | } 83 | public class BitcoinController : Pr0grammController 84 | { 85 | internal BitcoinController(IPr0grammApiClient client) 86 | : base(client) 87 | { } 88 | public Task GetPaymentAddress(string emailAddress, string product, bool termsAccepted) 89 | { 90 | if (string.IsNullOrWhiteSpace(emailAddress)) 91 | throw new ArgumentNullException(nameof(emailAddress)); 92 | if (string.IsNullOrWhiteSpace(product)) 93 | throw new ArgumentNullException(nameof(product)); 94 | Debug.Assert(product == Pr0miumProducts.ThreeMonths || product == Pr0miumProducts.TwelveMonths); 95 | return Client.Bitcoin.GetPaymentAddress(new PaymentData(Client.GetCurrentNonce(), emailAddress, product, termsAccepted)); 96 | } 97 | } 98 | public class CommentController : Pr0grammController 99 | { 100 | internal CommentController(IPr0grammApiClient client) 101 | : base(client) 102 | { } 103 | public Task Delete(IPr0grammComment comment, string reason) 104 | { 105 | if (comment == null) 106 | throw new ArgumentNullException(nameof(comment)); 107 | return Delete(comment.Id, reason); 108 | } 109 | public Task Delete(int commentId, string reason) 110 | { 111 | Debug.Assert(!string.IsNullOrWhiteSpace(reason)); 112 | return Client.Comments.Delete(new DeleteCommentData(Client.GetCurrentNonce(), commentId, reason)); 113 | } 114 | 115 | public Task SoftDelete(IPr0grammComment comment, string reason) 116 | { 117 | if (comment == null) 118 | throw new ArgumentNullException(nameof(comment)); 119 | return SoftDelete(comment.Id, reason); 120 | } 121 | public Task SoftDelete(int commentId, string reason) 122 | { 123 | Debug.Assert(!string.IsNullOrWhiteSpace(reason)); 124 | return Client.Comments.SoftDelete(new DeleteCommentData(Client.GetCurrentNonce(), commentId, reason)); 125 | } 126 | 127 | public Task Edit(IPr0grammComment comment, string newContent) 128 | { 129 | if (comment == null) 130 | throw new ArgumentNullException(nameof(comment)); 131 | return Edit(comment.Id, newContent); 132 | } 133 | public Task Edit(int commentId, string newContent) => Client.Comments.Edit(new EditCommentData(Client.GetCurrentNonce(), commentId, newContent)); 134 | 135 | public Task Post(IPr0grammItem item, string content) 136 | { 137 | if (item == null) 138 | throw new ArgumentNullException(nameof(item)); 139 | return Post(item.Id, content); 140 | } 141 | public Task Post(int itemId, string content) => Post(itemId, content, PostCommentData.NoParentId); 142 | public Task Post(IPr0grammItem item, string content, int parentId) 143 | { 144 | if (item == null) 145 | throw new ArgumentNullException(nameof(item)); 146 | return Post(item.Id, content, parentId); 147 | } 148 | public Task Post(int itemId, string content, int parentId) 149 | { 150 | if (string.IsNullOrWhiteSpace(content)) 151 | throw new ArgumentNullException(nameof(content)); 152 | return Client.Comments.Post(new PostCommentData(Client.GetCurrentNonce(), itemId, content, parentId)); 153 | } 154 | 155 | public Task Vote(IPr0grammComment comment, Vote absoluteVote) 156 | { 157 | if (comment == null) 158 | throw new ArgumentNullException(nameof(comment)); 159 | return Vote(comment.Id, absoluteVote); 160 | } 161 | public Task Vote(int commentId, Vote absoluteVote) => Client.Comments.Vote(new VoteData(Client.GetCurrentNonce(), commentId, (int)absoluteVote)); 162 | } 163 | public class InboxController : Pr0grammController 164 | { 165 | internal InboxController(IPr0grammApiClient client) 166 | : base(client) 167 | { } 168 | 169 | public Task GetOverview() => Client.Inbox.GetOverview(); 170 | public Task GetComments() => Client.Inbox.GetComments(); 171 | public Task GetNotifications() => Client.Inbox.GetNotifications(); 172 | public Task GetConversations(long? older = null) => Client.Inbox.GetConversations(older); 173 | public Task GetMessages(string with, long? older = null) => 174 | Client.Inbox.GetMessages(with, older); 175 | 176 | public Task SendMessage(IPr0grammUser recipient, string message) 177 | { 178 | if (recipient == null) 179 | throw new ArgumentNullException(nameof(recipient)); 180 | return SendMessage(recipient.Id, message); 181 | } 182 | public Task SendMessage(int recipientId, string message) 183 | { 184 | if (string.IsNullOrWhiteSpace(message)) 185 | throw new ArgumentNullException(nameof(message)); 186 | return Client.Inbox.PostMessage(new PrivateMessageData(Client.GetCurrentNonce(), recipientId, message)); 187 | } 188 | } 189 | public class ContactController : Pr0grammController 190 | { 191 | internal ContactController(IPr0grammApiClient client) 192 | : base(client) 193 | { } 194 | public Task SendMessage(string emailAddress, string subject, string content) 195 | { 196 | if (string.IsNullOrWhiteSpace(emailAddress)) 197 | throw new ArgumentNullException(nameof(emailAddress)); 198 | if (string.IsNullOrWhiteSpace(subject)) 199 | throw new ArgumentNullException(nameof(subject)); 200 | if (string.IsNullOrWhiteSpace(content)) 201 | throw new ArgumentNullException(nameof(content)); 202 | return Client.Contact.Send(new ContactData(Client.GetCurrentNonce(), emailAddress, subject, content)); 203 | } 204 | } 205 | public class PaypalController : Pr0grammController 206 | { 207 | internal PaypalController(IPr0grammApiClient client) 208 | : base(client) 209 | { } 210 | public Task GetCheckoutUrl(string emailAddress, string product, bool termsAccepted) 211 | { 212 | if (string.IsNullOrWhiteSpace(emailAddress)) 213 | throw new ArgumentNullException(nameof(emailAddress)); 214 | if (string.IsNullOrWhiteSpace(product)) 215 | throw new ArgumentNullException(nameof(product)); 216 | Debug.Assert(product == Pr0miumProducts.ThreeMonths || product == Pr0miumProducts.TwelveMonths); 217 | return Client.Paypal.GetCheckoutUrl(new PaymentData(Client.GetCurrentNonce(), emailAddress, product, termsAccepted)); 218 | } 219 | } 220 | public class ProfileController : Pr0grammController 221 | { 222 | internal ProfileController(IPr0grammApiClient client) 223 | : base(client) 224 | { } 225 | 226 | public Task GetCommentsBefore(INamedPr0grammUser user, ItemFlags flags, DateTime before) 227 | { 228 | if (user == null) 229 | throw new ArgumentNullException(nameof(user)); 230 | return GetCommentsBefore(user.Name, flags, before); 231 | } 232 | public Task GetCommentsBefore(string name, ItemFlags flags, DateTime before) 233 | { 234 | if (string.IsNullOrWhiteSpace(name)) 235 | throw new ArgumentNullException(nameof(name)); 236 | return Client.Profile.GetCommentsBefore(name, flags, unchecked((int)before.ToUnixTime())); 237 | } 238 | 239 | public Task GetCommentsAfter(INamedPr0grammUser user, ItemFlags flags, DateTime after) 240 | { 241 | if (user == null) 242 | throw new ArgumentNullException(nameof(user)); 243 | return GetCommentsAfter(user.Name, flags, after); 244 | } 245 | public Task GetCommentsAfter(string name, ItemFlags flags, DateTime after) 246 | { 247 | if (string.IsNullOrWhiteSpace(name)) 248 | throw new ArgumentNullException(nameof(name)); 249 | return Client.Profile.GetCommentsAfter(name, flags, unchecked((int)after.ToUnixTime())); 250 | } 251 | 252 | public Task GetInfo(INamedPr0grammUser user, ItemFlags flags) 253 | { 254 | if (user == null) 255 | throw new ArgumentNullException(nameof(user)); 256 | return GetInfo(user.Name, flags); 257 | } 258 | public async Task GetInfo(string name, ItemFlags flags) 259 | { 260 | if (string.IsNullOrWhiteSpace(name)) 261 | throw new ArgumentNullException(nameof(name)); 262 | var info = await Client.Profile.GetInfo(name, flags); 263 | AddDynamicBadges(info); 264 | return info; 265 | } 266 | 267 | public Task Follow(INamedPr0grammUser userToFollow) 268 | { 269 | if (userToFollow == null) 270 | throw new ArgumentNullException(nameof(userToFollow)); 271 | return Follow(userToFollow.Name); 272 | } 273 | public Task Follow(string userToFollow) 274 | { 275 | if (string.IsNullOrWhiteSpace(userToFollow)) 276 | throw new ArgumentNullException(nameof(userToFollow)); 277 | return Client.Profile.Follow(new FollowData(Client.GetCurrentNonce(), userToFollow)); 278 | } 279 | 280 | public Task Unfollow(INamedPr0grammUser userToUnfollow) 281 | { 282 | if (userToUnfollow == null) 283 | throw new ArgumentNullException(nameof(userToUnfollow)); 284 | return Unfollow(userToUnfollow.Name); 285 | } 286 | public Task Unfollow(string userToUnfollow) 287 | { 288 | if (string.IsNullOrWhiteSpace(userToUnfollow)) 289 | throw new ArgumentNullException(nameof(userToUnfollow)); 290 | return Client.Profile.Unfollow(new FollowData(Client.GetCurrentNonce(), userToUnfollow)); 291 | } 292 | 293 | private static void AddDynamicBadges(GetProfileInfoResponse info) 294 | { 295 | if (info?.Badges == null) 296 | return; 297 | 298 | var badges = info.Badges; 299 | var newBadges = new List(badges); 300 | 301 | var firstComment = info.Comments.Select(c => c?.CreatedAt).FirstOrDefault(); 302 | var commentBadge = DynamicProfileBadge.CreateFromCommentCount(info.User.Name, info.CommentCount, firstComment.HasValue ? firstComment.Value : DateTime.Now); 303 | if (commentBadge != null) 304 | newBadges.Add(commentBadge); 305 | 306 | var yearsBadge = DynamicProfileBadge.CreateFromRegistrationDate(info.User.Name, info.User.RegisteredSince); 307 | if (yearsBadge != null) 308 | newBadges.Add(yearsBadge); 309 | 310 | info.Badges = newBadges; 311 | } 312 | } 313 | public class UserController : Pr0grammController 314 | { 315 | internal UserController(IPr0grammApiClient client) 316 | : base(client) 317 | { } 318 | public Task Ban(INamedPr0grammUser user, string reason, int durationInDays) 319 | { 320 | if (user == null) 321 | throw new ArgumentNullException(nameof(user)); 322 | return Ban(user.Name, reason, durationInDays); 323 | } 324 | public Task Ban(string user, string reason, int durationInDays) 325 | { 326 | if (durationInDays < 0) 327 | throw new ArgumentException($"{nameof(durationInDays)} must be >= 0."); 328 | return Client.User.Ban(new BanData(Client.GetCurrentNonce(), user, reason, durationInDays)); 329 | } 330 | 331 | public Task ChangeEmail(string token) 332 | { 333 | if (string.IsNullOrEmpty(token)) 334 | throw new ArgumentNullException(nameof(token)); 335 | return Client.User.ChangeEmail(new ChangeEmailData(Client.GetCurrentNonce(), token)); 336 | } 337 | public Task ChangePassword(string currentPassword, string newPassword) 338 | { 339 | if (string.IsNullOrEmpty(currentPassword)) 340 | throw new ArgumentNullException(nameof(currentPassword)); 341 | if (string.IsNullOrEmpty(newPassword)) 342 | throw new ArgumentNullException(nameof(newPassword)); 343 | return Client.User.ChangePassword(new ChangePasswordData(Client.GetCurrentNonce(), currentPassword, newPassword)); 344 | } 345 | public Task GetFollowList(ItemFlags flags) => Client.User.GetFollowList(flags); 346 | public Task GetInfo() => Client.User.GetInfo(); 347 | public Task Invite(string email) 348 | { 349 | if (string.IsNullOrWhiteSpace(email)) 350 | throw new ArgumentNullException(nameof(email)); 351 | return Client.User.Invite(new InviteData(Client.GetCurrentNonce(), email)); 352 | } 353 | public Task JoinWithInvite(string email, string name, string password, string token) 354 | { 355 | if (string.IsNullOrWhiteSpace(token)) 356 | throw new ArgumentNullException(nameof(token)); 357 | if (string.IsNullOrWhiteSpace(name)) 358 | throw new ArgumentNullException(nameof(name)); 359 | if (password.Length < 2) 360 | throw new ArgumentException($"{nameof(name)}.Length must be >= 2."); 361 | if (string.IsNullOrWhiteSpace(email)) 362 | throw new ArgumentNullException(nameof(email)); 363 | if (string.IsNullOrEmpty(password)) 364 | throw new ArgumentNullException(nameof(password)); 365 | if (password.Length < 6) 366 | throw new ArgumentException($"{nameof(password)}.Length must be >= 6."); 367 | return Client.User.JoinWithInvite(new JoinWithTokenData(email, name, password, token)); 368 | } 369 | public Task JoinWithToken(string email, string name, string password, string token) 370 | { 371 | if (string.IsNullOrWhiteSpace(token)) 372 | throw new ArgumentNullException(nameof(token)); 373 | if (string.IsNullOrWhiteSpace(name)) 374 | throw new ArgumentNullException(nameof(name)); 375 | if (password.Length < 2) 376 | throw new ArgumentException($"{nameof(name)}.Length must be >= 2."); 377 | if (string.IsNullOrWhiteSpace(email)) 378 | throw new ArgumentNullException(nameof(email)); 379 | if (string.IsNullOrEmpty(password)) 380 | throw new ArgumentNullException(nameof(password)); 381 | if (password.Length < 6) 382 | throw new ArgumentException($"{nameof(password)}.Length must be >= 6."); 383 | return Client.User.JoinWithToken(new JoinWithTokenData(email, name, password, token)); 384 | } 385 | public Task LoadInvite(string token) 386 | { 387 | if (string.IsNullOrEmpty(token)) 388 | throw new ArgumentNullException(nameof(token)); 389 | return Client.User.LoadInvite(token); 390 | } 391 | public Task LoadPaymentToken(string token) 392 | { 393 | if (string.IsNullOrWhiteSpace(token)) 394 | throw new ArgumentNullException(nameof(token)); 395 | return Client.User.LoadPaymentToken(new TokenActionData(Client.GetCurrentNonce(), token)); 396 | } 397 | 398 | public Task LoggedIn() => Client.User.LoggedIn(); 399 | 400 | public Task LogIn(string name, string password, string captchaToken, string captchaSolution) 401 | { 402 | if (string.IsNullOrWhiteSpace(name)) 403 | throw new ArgumentNullException(nameof(name)); 404 | if (string.IsNullOrEmpty(password)) 405 | throw new ArgumentNullException(nameof(password)); 406 | if (string.IsNullOrEmpty(captchaToken)) 407 | throw new ArgumentNullException(nameof(captchaToken)); 408 | if (string.IsNullOrEmpty(captchaSolution)) 409 | throw new ArgumentNullException(nameof(captchaSolution)); 410 | return Client.User.LogIn(new LogInData(name, password, captchaToken, captchaSolution)); 411 | } 412 | public Task LogOut() => Client.User.LogOut(new LogOutData(Client.GetCurrentNonce(), Client.GetCurrentSessionId())); 413 | 414 | public Task RequestCaptcha() 415 | { 416 | return Client.User.RequestCaptcha(); 417 | } 418 | public Task RedeemToken(string token) 419 | { 420 | if (string.IsNullOrWhiteSpace(token)) 421 | throw new ArgumentNullException(nameof(token)); 422 | return Client.User.RedeemToken(new TokenActionData(Client.GetCurrentNonce(), token)); 423 | } 424 | public Task RequestEmailChange(string currentPassword, string newEmailAddress) 425 | { 426 | if (string.IsNullOrEmpty(currentPassword)) 427 | throw new ArgumentNullException(nameof(currentPassword)); 428 | if (string.IsNullOrWhiteSpace(newEmailAddress)) 429 | throw new ArgumentNullException(nameof(newEmailAddress)); 430 | return Client.User.RequestEmailChange(new RequestEmailChangeData(Client.GetCurrentNonce(), currentPassword, newEmailAddress)); 431 | 432 | } 433 | public Task ResetPassword(string token, string name, string newPassword) 434 | { 435 | if (string.IsNullOrWhiteSpace(token)) 436 | throw new ArgumentNullException(nameof(token)); 437 | if (string.IsNullOrWhiteSpace(name)) 438 | throw new ArgumentNullException(nameof(name)); 439 | if (string.IsNullOrEmpty(newPassword)) 440 | throw new ArgumentNullException(nameof(newPassword)); 441 | if (newPassword.Length < 6) 442 | throw new ArgumentException($"{nameof(newPassword)}.Length must be >= 6."); 443 | return Client.User.ResetPassword(new ResetPasswordData(token, name, newPassword)); 444 | } 445 | public Task SendPasswordResetMail(string email) 446 | { 447 | if (string.IsNullOrWhiteSpace(email)) 448 | throw new ArgumentNullException(email); 449 | return Client.User.SendPasswordResetMail(new SendPasswordResetMailData(email)); 450 | } 451 | public Task SetSiteSettings(bool likesArePublic, bool showAds, UserStatus userStatus) 452 | { 453 | if (!Enum.IsDefined(typeof(UserStatus), userStatus)) 454 | throw new ArgumentException($"Invalid value for {nameof(userStatus)}"); 455 | var statusStr = userStatus.ToString().ToLowerInvariant(); 456 | return Client.User.SetSiteSettings(new SiteSettingsData(Client.GetCurrentNonce(), likesArePublic, showAds, statusStr)); 457 | } 458 | public Task Sync(int lastId) => Client.User.Sync(lastId); 459 | public Task Validate(string token) 460 | { 461 | if (string.IsNullOrWhiteSpace(token)) 462 | return Task.FromResult(new SuccessableResponse(false)); // TODO: Or throw exception? 463 | return Client.User.Validate(new TokenActionData(Client.GetCurrentNonce(), token)); 464 | } 465 | } 466 | public class TagController : Pr0grammController 467 | { 468 | internal TagController(IPr0grammApiClient client) 469 | : base(client) 470 | { } 471 | public Task Add(IPr0grammItem item, string tag) 472 | { 473 | if (item == null) 474 | throw new ArgumentNullException(nameof(item)); 475 | return Add(item.Id, tag); 476 | } 477 | public Task Add(int itemId, string tag) => Add(itemId, new[] { tag }); 478 | public Task Add(IPr0grammItem item, params string[] tags) 479 | { 480 | if (item == null) 481 | throw new ArgumentNullException(nameof(item)); 482 | return Add(item.Id, tags); 483 | } 484 | public Task Add(int itemId, params string[] tags) 485 | { 486 | if (itemId < 0) 487 | throw new ArgumentException($"{nameof(itemId)} must be >= 0."); 488 | return Client.Tags.Add(new AddTagsData(Client.GetCurrentNonce(), itemId, tags)); 489 | } 490 | 491 | public Task Delete(IPr0grammItem item, bool banUsers, params IPr0grammTag[] tags) 492 | { 493 | if (item == null) 494 | throw new ArgumentNullException(nameof(item)); 495 | return Delete(item.Id, banUsers, tags.Select(t => t.Id).ToArray()); 496 | } 497 | public Task Delete(int itemId, bool banUsers, params int[] tagIds) => Delete(itemId, banUsers, 1, tagIds); 498 | public Task Delete(IPr0grammItem item, bool banUsers, int durationInDays, params IPr0grammTag[] tags) 499 | { 500 | if (item == null) 501 | throw new ArgumentNullException(nameof(item)); 502 | return Delete(item.Id, banUsers, durationInDays, tags.Select(t => t.Id).ToArray()); 503 | } 504 | public Task Delete(int itemId, bool banUsers, int durationInDays, params int[] tagIds) 505 | { 506 | if (tagIds == null || tagIds.Length == 0) 507 | throw new ArgumentException($"{tagIds} cannot be empty."); 508 | return Client.Tags.Delete(new DeleteTagsData(Client.GetCurrentNonce(), itemId, banUsers, durationInDays, tagIds)); 509 | } 510 | 511 | public Task GetDetails(IPr0grammItem item) 512 | { 513 | if (item == null) 514 | throw new ArgumentNullException(nameof(item)); 515 | return GetDetails(item.Id); 516 | } 517 | public Task GetDetails(int itemId) 518 | { 519 | if (itemId < 0) 520 | throw new ArgumentException($"{nameof(itemId)} must be >= 0."); 521 | return Client.Tags.GetDetails(itemId); 522 | } 523 | 524 | public Task Vote(IPr0grammTag tag, Vote absoluteVote) 525 | { 526 | if (tag == null) 527 | throw new ArgumentNullException(nameof(tag)); 528 | return Vote(tag.Id, absoluteVote); 529 | } 530 | public Task Vote(int tagId, Vote absoluteVote) 531 | { 532 | if (tagId < 0) 533 | throw new ArgumentException($"{nameof(tagId)} must be >= 0."); 534 | return Client.Tags.Vote(new VoteData(Client.GetCurrentNonce(), tagId, (int)absoluteVote)); 535 | } 536 | } 537 | public class ItemController : Pr0grammController 538 | { 539 | internal ItemController(IPr0grammApiClient client) 540 | : base(client) 541 | { } 542 | 543 | public Task Delete(IPr0grammItem item, string reason, bool notifyUser, bool banUser, int days) 544 | { 545 | if (item == null) 546 | throw new ArgumentNullException(nameof(item)); 547 | return Delete(item.Id, reason, notifyUser, banUser, days); 548 | } 549 | public Task Delete(int itemId, string reason, bool notifyUser, bool banUser, int days) 550 | { 551 | if (itemId < 0) 552 | throw new ArgumentException($"{nameof(itemId)} must be >= 0."); 553 | if (days < 0) 554 | throw new ArgumentException($"{nameof(days)} must be >= 0."); 555 | // REMARKS: reason == customreason 556 | return Client.Items.Delete(new DeleteItemData(Client.GetCurrentNonce(), itemId, reason, reason, notifyUser, banUser, days)); 557 | } 558 | 559 | public Task GetItems(ItemFlags flags, ItemStatus status) => GetItems(flags, status, false, null, null, null, false); 560 | public Task GetItemsByTag(ItemFlags flags, ItemStatus status, string tags) => GetItems(flags, status, false, tags, null, null, false); 561 | public Task GetItemsByUser(ItemFlags flags, ItemStatus status, INamedPr0grammUser user) 562 | { 563 | if (user == null) 564 | throw new ArgumentNullException(nameof(user)); 565 | return GetItemsByUser(flags, status, user.Name); 566 | } 567 | public Task GetItemsByUser(ItemFlags flags, ItemStatus status, string user) => GetItems(flags, status, false, null, user, null, false); 568 | public Task GetItemsByCollection(ItemFlags flags, ItemStatus status, string user, string collection) => GetItems(flags, status, false, null, user, collection, false); 569 | public Task GetItemsBySelf(ItemFlags flags, ItemStatus status) => GetItems(flags, status, false, null, null, null, true); 570 | internal Task GetItems(ItemFlags flags, ItemStatus status, bool following, string tags, string user, string collection, bool self) 571 | { 572 | return Client.Items.GetItems( 573 | flags, 574 | (int)status, 575 | following ? 1 : 0, 576 | string.IsNullOrEmpty(tags) ? null : tags, 577 | string.IsNullOrEmpty(user) ? null : user, 578 | string.IsNullOrEmpty(collection) ? null : collection, 579 | self ? 1 : 0); 580 | } 581 | 582 | public Task GetItemsNewer(ItemFlags flags, ItemStatus status, bool following, string tags, string user, string collection, bool self, IPr0grammItem newerThan) 583 | { 584 | if (newerThan == null) 585 | throw new ArgumentNullException(nameof(newerThan)); 586 | return GetItemsNewer(flags, status, following, tags, user, collection, self, newerThan.Id); 587 | } 588 | public Task GetItemsNewer(ItemFlags flags, ItemStatus status, bool following, string tags, string user, string collection, bool self, int newerThan) 589 | { 590 | return Client.Items.GetItemsNewer(flags, 591 | (int)status, 592 | following ? 1 : 0, 593 | string.IsNullOrEmpty(tags) ? null : tags, 594 | string.IsNullOrEmpty(user) ? null : user, 595 | string.IsNullOrEmpty(collection) ? null : collection, 596 | self ? 1 : 0, 597 | newerThan); 598 | } 599 | 600 | public Task GetItemsOlder(ItemFlags flags, ItemStatus status, bool following, string tags, string user, string collection, bool self, IPr0grammItem olderThan) 601 | { 602 | if (olderThan == null) 603 | throw new ArgumentNullException(nameof(olderThan)); 604 | return GetItemsOlder(flags, status, following, tags, user, collection, self, olderThan.Id); 605 | } 606 | public Task GetItemsOlder(ItemFlags flags, ItemStatus status, bool following, string tags, string user, string collection, bool self, int olderThan) 607 | { 608 | return Client.Items.GetItemsOlder(flags, 609 | (int)status, 610 | following ? 1 : 0, 611 | string.IsNullOrEmpty(tags) ? null : tags, 612 | string.IsNullOrEmpty(user) ? null : user, 613 | string.IsNullOrEmpty(collection) ? null : collection, 614 | self ? 1 : 0, 615 | olderThan); 616 | } 617 | 618 | public Task GetItemsAround(ItemFlags flags, ItemStatus status, bool following, string tags, string user, string collection, bool self, IPr0grammItem aroundItem) 619 | { 620 | if (aroundItem == null) 621 | throw new ArgumentNullException(nameof(aroundItem)); 622 | return GetItemsAround(flags, status, following, tags, user, collection, self, aroundItem.Id); 623 | } 624 | public Task GetItemsAround(ItemFlags flags, ItemStatus status, bool following, string tags, string user, string collection, bool self, int aroundId) 625 | { 626 | return Client.Items.GetItemsAround(flags, 627 | (int)status, 628 | following ? 1 : 0, 629 | string.IsNullOrEmpty(tags) ? null : tags, 630 | string.IsNullOrEmpty(user) ? null : user, 631 | string.IsNullOrEmpty(collection) ? null : collection, 632 | self ? 1 : 0, 633 | aroundId); 634 | } 635 | 636 | public Task Vote(IPr0grammItem item) 637 | { 638 | if (item == null) 639 | throw new ArgumentNullException(nameof(item)); 640 | return GetInfo(item.Id); 641 | } 642 | public Task GetInfo(int itemId) 643 | { 644 | if (itemId < 0) 645 | throw new ArgumentException($"{nameof(itemId)} must be >= 0."); 646 | return Client.Items.GetInfo(itemId); 647 | } 648 | 649 | public Task RateLimited() => Client.Items.RateLimited(); 650 | 651 | public Task Vote(IPr0grammItem item, Vote absoluteVote) 652 | { 653 | if (item == null) 654 | throw new ArgumentNullException(nameof(item)); 655 | return Vote(item.Id, absoluteVote); 656 | } 657 | public Task Vote(int itemId, Vote absoluteVote) 658 | { 659 | if (itemId < 0) 660 | throw new ArgumentException($"{nameof(itemId)} must be >= 0."); 661 | return Client.Items.Vote(new VoteData(Client.GetCurrentNonce(), itemId, (int)absoluteVote)); 662 | } 663 | } 664 | 665 | public enum UserStatus 666 | { 667 | Default, 668 | Paid 669 | } 670 | } 671 | --------------------------------------------------------------------------------