├── .gitignore ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── NeteaseCloudMusicApi.cs ├── README.md ├── YunPlugin-UNM.cs ├── YunPlugin-UNM.csproj ├── YunSettings.ini ├── docker └── Dockerfile └── lib ├── Nini.dll ├── TS3AudioBot.dll └── TSLib.dll /.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 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the -------------------------------------------------------------------------------- /FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks 13 | 14 | 15 | 16 | 17 | A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. 18 | 19 | 20 | 21 | 22 | A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks 23 | 24 | 25 | 26 | 27 | A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. 28 | 29 | 30 | 31 | 32 | A list of unmanaged 32 bit assembly names to include, delimited with line breaks. 33 | 34 | 35 | 36 | 37 | A list of unmanaged 64 bit assembly names to include, delimited with line breaks. 38 | 39 | 40 | 41 | 42 | The order of preloaded assemblies, delimited with line breaks. 43 | 44 | 45 | 46 | 47 | 48 | This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. 49 | 50 | 51 | 52 | 53 | Controls if .pdbs for reference assemblies are also embedded. 54 | 55 | 56 | 57 | 58 | Controls if runtime assemblies are also embedded. 59 | 60 | 61 | 62 | 63 | Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. 64 | 65 | 66 | 67 | 68 | Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. 69 | 70 | 71 | 72 | 73 | As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. 74 | 75 | 76 | 77 | 78 | Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. 79 | 80 | 81 | 82 | 83 | Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. 84 | 85 | 86 | 87 | 88 | A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | 89 | 90 | 91 | 92 | 93 | A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. 94 | 95 | 96 | 97 | 98 | A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | 99 | 100 | 101 | 102 | 103 | A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. 104 | 105 | 106 | 107 | 108 | A list of unmanaged 32 bit assembly names to include, delimited with |. 109 | 110 | 111 | 112 | 113 | A list of unmanaged 64 bit assembly names to include, delimited with |. 114 | 115 | 116 | 117 | 118 | The order of preloaded assemblies, delimited with |. 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 127 | 128 | 129 | 130 | 131 | A comma-separated list of error codes that can be safely ignored in assembly verification. 132 | 133 | 134 | 135 | 136 | 'false' to turn off automatic generation of the XML Schema file. 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /NeteaseCloudMusicApi.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NeteaseCloudMusicApi 4 | { 5 | public class ArtistsItem 6 | { 7 | public long id { get; set; } 8 | 9 | public string name { get; set; } 10 | 11 | public string picUrl; 12 | 13 | public List @alias; 14 | 15 | public long albumSize; 16 | 17 | public long picId; 18 | 19 | public string fansGroup; 20 | 21 | public string img1v1Url; 22 | 23 | public long img1v1; 24 | 25 | public string trans; 26 | } 27 | 28 | public class Artist 29 | { 30 | 31 | public long id; 32 | 33 | public string name; 34 | 35 | public string picUrl; 36 | 37 | public List @alias; 38 | 39 | public long albumSize; 40 | 41 | public long picId; 42 | 43 | public string fansGroup; 44 | 45 | public string img1v1Url; 46 | 47 | public long img1v1; 48 | 49 | public string trans; 50 | } 51 | 52 | public class Album 53 | { 54 | 55 | public long id; 56 | 57 | public string name; 58 | 59 | public Artist artist; 60 | 61 | public long publishTime; 62 | 63 | public long size; 64 | 65 | public long copyrightId; 66 | 67 | public long status; 68 | 69 | public long picId; 70 | 71 | public long mark; 72 | } 73 | 74 | public class SongsItem 75 | { 76 | 77 | public long id { get; set; } 78 | 79 | public string name { get; set; } 80 | 81 | public List artists { get; set; } 82 | 83 | public Album album; 84 | 85 | public long duration; 86 | 87 | public long copyrightId; 88 | 89 | public long status; 90 | 91 | public List @alias; 92 | 93 | public long rtype; 94 | 95 | public long ftype; 96 | 97 | public long mvid; 98 | 99 | public long fee; 100 | 101 | public string rUrl; 102 | 103 | public long mark; 104 | } 105 | 106 | public class Result 107 | { 108 | 109 | public List songs { get; set; } 110 | 111 | public bool hasMore; 112 | 113 | public long songCount; 114 | } 115 | 116 | public class yunSearchSong 117 | { 118 | 119 | public Result result { get; set; } 120 | 121 | public long code; 122 | } 123 | 124 | public class FreeTrialInfo 125 | { 126 | 127 | public long start; 128 | 129 | public long end; 130 | } 131 | 132 | public class FreeTrialPrivilege 133 | { 134 | 135 | public bool resConsumable; 136 | 137 | public bool userConsumable; 138 | 139 | public string listenType; 140 | 141 | public string cannotListenReason; 142 | } 143 | 144 | public class FreeTimeTrialPrivilege 145 | { 146 | 147 | public bool resConsumable; 148 | 149 | public bool userConsumable; 150 | 151 | public long type; 152 | 153 | public long remalongime; 154 | } 155 | 156 | public class DataItem 157 | { 158 | 159 | public long id; 160 | 161 | public string url { get; set; } 162 | 163 | public long br; 164 | 165 | public long size; 166 | 167 | public string md5; 168 | 169 | public long code; 170 | 171 | public long expi; 172 | 173 | public string type; 174 | 175 | public double gain; 176 | 177 | public double peak; 178 | 179 | public long fee; 180 | 181 | public string uf; 182 | 183 | public long payed; 184 | 185 | public long flag; 186 | 187 | public bool canExtend; 188 | 189 | public FreeTrialInfo freeTrialInfo; 190 | 191 | public string level; 192 | 193 | public string encodeType; 194 | 195 | public FreeTrialPrivilege freeTrialPrivilege; 196 | 197 | public FreeTimeTrialPrivilege freeTimeTrialPrivilege; 198 | 199 | public long urlSource; 200 | 201 | public long rightSource; 202 | 203 | public string podcastCtrp; 204 | 205 | public string effectTypes; 206 | 207 | public long time; 208 | } 209 | 210 | public class musicURL 211 | { 212 | 213 | public List data { get; set; } 214 | 215 | public long code { get; set; } 216 | } 217 | 218 | public class ArItem 219 | { 220 | 221 | public long id; 222 | 223 | public string name; 224 | 225 | public List tns; 226 | 227 | public List @alias; 228 | } 229 | 230 | public class Al 231 | { 232 | 233 | public long id; 234 | 235 | public string name; 236 | 237 | public string picUrl; 238 | 239 | public List tns; 240 | 241 | public long pic; 242 | } 243 | 244 | public class H 245 | { 246 | 247 | public long br; 248 | 249 | public long fid; 250 | 251 | public long size; 252 | 253 | public long vd; 254 | 255 | public long sr; 256 | } 257 | 258 | public class M 259 | { 260 | 261 | public long br; 262 | 263 | public long fid; 264 | 265 | public long size; 266 | 267 | public long vd; 268 | 269 | public long sr; 270 | } 271 | 272 | public class L 273 | { 274 | 275 | public long br; 276 | 277 | public long fid; 278 | 279 | public long size; 280 | 281 | public long vd; 282 | 283 | public long sr; 284 | } 285 | 286 | public class SongsItems 287 | { 288 | 289 | public string name { get; set; } 290 | 291 | public long id { get; set; } 292 | 293 | public long pst; 294 | 295 | public long t; 296 | 297 | public List ar; 298 | 299 | public List alia; 300 | 301 | public long pop; 302 | 303 | public long st; 304 | 305 | public string rt; 306 | 307 | public long fee; 308 | 309 | public long v; 310 | 311 | public string crbt; 312 | 313 | public string cf; 314 | 315 | public Al al; 316 | 317 | public long dt; 318 | 319 | public H h; 320 | 321 | public M m; 322 | 323 | public L l; 324 | 325 | public string sq; 326 | 327 | public string hr; 328 | 329 | public string a; 330 | 331 | public string cd; 332 | 333 | public long no; 334 | 335 | public string rtUrl; 336 | 337 | public long ftype; 338 | 339 | public List rtUrls; 340 | 341 | public long djId; 342 | 343 | public long copyright; 344 | 345 | public long s_id; 346 | 347 | public long mark; 348 | 349 | public long originCoverType; 350 | 351 | public string originSongSimpleData; 352 | 353 | public string tagPicList; 354 | 355 | public bool resourceState; 356 | 357 | public long version; 358 | 359 | public string songJumpInfo; 360 | 361 | public string entertainmentTags; 362 | 363 | public string awardTags; 364 | 365 | public long single; 366 | 367 | public bool noCopyrightRcmd; 368 | 369 | public long mst; 370 | 371 | public long cp; 372 | 373 | public long rtype; 374 | 375 | public string rurl; 376 | 377 | public long mv; 378 | 379 | public long publishTime; 380 | } 381 | 382 | public class FreeTrial 383 | { 384 | 385 | public bool resConsumable; 386 | 387 | public bool userConsumable; 388 | 389 | public string listenType; 390 | } 391 | 392 | public class ChargeInfoListItem 393 | { 394 | 395 | public long rate; 396 | 397 | public string chargeUrl; 398 | 399 | public string chargeMessage; 400 | 401 | public long chargeType; 402 | } 403 | 404 | public class PrivilegesItem 405 | { 406 | 407 | public long id; 408 | 409 | public long fee; 410 | 411 | public long payed; 412 | 413 | public long st; 414 | 415 | public long pl; 416 | 417 | public long dl; 418 | 419 | public long sp; 420 | 421 | public long cp; 422 | 423 | public long subp; 424 | 425 | public bool cs; 426 | 427 | public long maxbr; 428 | 429 | public long fl; 430 | 431 | public bool toast; 432 | 433 | public long flag; 434 | 435 | public bool preSell; 436 | 437 | public long playMaxbr; 438 | 439 | public long downloadMaxbr; 440 | 441 | public string maxBrLevel; 442 | 443 | public string playMaxBrLevel; 444 | 445 | public string downloadMaxBrLevel; 446 | 447 | public string plLevel; 448 | 449 | public string dlLevel; 450 | 451 | public string flLevel; 452 | 453 | public string rscl; 454 | 455 | public FreeTrial freeTrialPrivilege; 456 | 457 | public List chargeInfoList; 458 | } 459 | 460 | public class GeDan 461 | { 462 | 463 | public List songs { get; set; } 464 | 465 | public List privileges; 466 | 467 | public long code; 468 | } 469 | 470 | public class Creator 471 | { 472 | /// 473 | /// 淋雨丶伞 474 | /// 475 | public string nickname; 476 | 477 | public long userId; 478 | 479 | public long userType; 480 | 481 | public string avatarUrl; 482 | 483 | public long authStatus; 484 | 485 | public string expertTags; 486 | 487 | public string experts; 488 | } 489 | 490 | public class ArtistsItems 491 | { 492 | 493 | public string name; 494 | 495 | public long id; 496 | 497 | public long picId; 498 | 499 | public long img1v1Id; 500 | 501 | public string briefDesc; 502 | 503 | public string picUrl; 504 | 505 | public string img1v1Url; 506 | 507 | public long albumSize; 508 | 509 | public List @alias; 510 | 511 | public string trans; 512 | 513 | public long musicSize; 514 | } 515 | 516 | public class Artists 517 | { 518 | 519 | public string name; 520 | 521 | public long id; 522 | 523 | public long picId; 524 | 525 | public long img1v1Id; 526 | 527 | public string briefDesc; 528 | 529 | public string picUrl; 530 | 531 | public string img1v1Url; 532 | 533 | public long albumSize; 534 | 535 | public List @alias; 536 | 537 | public string trans; 538 | 539 | public long musicSize; 540 | } 541 | 542 | public class ArtistsItemss 543 | { 544 | 545 | public string name; 546 | 547 | public long id; 548 | 549 | public long picId; 550 | 551 | public long img1v1Id; 552 | 553 | public string briefDesc; 554 | 555 | public string picUrl; 556 | 557 | public string img1v1Url; 558 | 559 | public long albumSize; 560 | 561 | public List @alias; 562 | 563 | public string trans; 564 | 565 | public long musicSize; 566 | } 567 | 568 | public class Albums 569 | { 570 | 571 | public string name; 572 | 573 | public long id; 574 | 575 | public string idStr; 576 | /// 577 | /// 专辑 578 | /// 579 | public string type; 580 | 581 | public long size; 582 | 583 | public long picId; 584 | 585 | public string blurPicUrl; 586 | 587 | public long companyId; 588 | 589 | public long pic; 590 | 591 | public string picUrl; 592 | 593 | public long publishTime; 594 | 595 | public string description; 596 | 597 | public string tags; 598 | 599 | public string company; 600 | 601 | public string briefDesc; 602 | 603 | public Artist artist; 604 | 605 | public List songs; 606 | 607 | public List @alias; 608 | 609 | public long status; 610 | 611 | public long copyrightId; 612 | 613 | public string commentThreadId; 614 | 615 | public List artists; 616 | } 617 | 618 | public class BMusic 619 | { 620 | 621 | public string name; 622 | 623 | public long id; 624 | 625 | public long size; 626 | 627 | public string extension; 628 | 629 | public long sr; 630 | 631 | public long dfsId; 632 | 633 | public long bitrate; 634 | 635 | public long playTime; 636 | 637 | public long volumeDelta; 638 | } 639 | 640 | public class HMusic 641 | { 642 | 643 | public string name; 644 | 645 | public long id; 646 | 647 | public long size; 648 | 649 | public string extension; 650 | 651 | public long sr; 652 | 653 | public long dfsId; 654 | 655 | public long bitrate; 656 | 657 | public long playTime; 658 | 659 | public long volumeDelta; 660 | } 661 | 662 | public class MMusic 663 | { 664 | 665 | public string name; 666 | 667 | public long id; 668 | 669 | public long size; 670 | 671 | public string extension; 672 | 673 | public long sr; 674 | 675 | public long dfsId; 676 | 677 | public long bitrate; 678 | 679 | public long playTime; 680 | 681 | public long volumeDelta; 682 | } 683 | 684 | public class LMusic 685 | { 686 | 687 | public string name; 688 | 689 | public long id; 690 | 691 | public long size; 692 | 693 | public string extension; 694 | 695 | public long sr; 696 | 697 | public long dfsId; 698 | 699 | public long bitrate; 700 | 701 | public long playTime; 702 | 703 | public long volumeDelta; 704 | } 705 | 706 | public class Track 707 | { 708 | 709 | public string name; 710 | 711 | public long id; 712 | 713 | public long position; 714 | 715 | public List @alias; 716 | 717 | public long status; 718 | 719 | public long fee; 720 | 721 | public long copyrightId; 722 | 723 | public string disc; 724 | 725 | public long no; 726 | 727 | public List artists; 728 | 729 | public Albums album; 730 | 731 | public bool starred; 732 | 733 | public long popularity; 734 | 735 | public long score; 736 | 737 | public long starredNum; 738 | 739 | public long duration; 740 | 741 | public long playedNum; 742 | 743 | public long dayPlays; 744 | 745 | public long hearTime; 746 | 747 | public string ringtone; 748 | 749 | public string crbt; 750 | 751 | public string audition; 752 | 753 | public string copyFrom; 754 | 755 | public string commentThreadId; 756 | 757 | public string rtUrl; 758 | 759 | public long ftype; 760 | 761 | public List rtUrls; 762 | 763 | public long copyright; 764 | 765 | public long rtype; 766 | 767 | public string rurl; 768 | 769 | public BMusic bMusic; 770 | 771 | public string mp3Url; 772 | 773 | public long mvid; 774 | 775 | public HMusic hMusic; 776 | 777 | public MMusic mMusic; 778 | 779 | public LMusic lMusic; 780 | } 781 | 782 | public class PlaylistsItem 783 | { 784 | 785 | public long id { get; set; } 786 | 787 | public string name; 788 | 789 | public string coverImgUrl; 790 | 791 | public Creator creator; 792 | 793 | public bool subscribed; 794 | 795 | public long trackCount; 796 | 797 | public long userId; 798 | 799 | public long playCount; 800 | 801 | public long bookCount; 802 | 803 | public long specialType; 804 | 805 | public List officialTags; 806 | 807 | public string action; 808 | 809 | public string actionType; 810 | 811 | public string recommendText; 812 | 813 | public string score; 814 | 815 | public string description; 816 | 817 | public bool highQuality; 818 | 819 | public Track track; 820 | 821 | public string alg; 822 | } 823 | 824 | public class Results 825 | { 826 | 827 | public List playlists { get; set; } 828 | 829 | public bool hasMore; 830 | 831 | public List hlWords; 832 | 833 | public long playlistCount; 834 | 835 | public string searchQcReminder; 836 | } 837 | 838 | public class SearchGedan 839 | { 840 | 841 | public Results result { get; set; } 842 | 843 | public long code; 844 | } 845 | 846 | public class Data 847 | { 848 | 849 | public int code; 850 | 851 | public string unikey { get; set; } 852 | } 853 | 854 | public class LoginKey 855 | { 856 | 857 | public Data data { get; set; } 858 | 859 | public int code; 860 | } 861 | 862 | public class Datas 863 | { 864 | 865 | public string qrurl; 866 | 867 | public string qrimg { get; set; } 868 | } 869 | 870 | public class LoginImg 871 | { 872 | 873 | public int code; 874 | 875 | public Datas data { get; set; } 876 | } 877 | 878 | public class Status1 879 | { 880 | 881 | public long code { get; set; } 882 | 883 | public string message { get; set; } 884 | 885 | public string cookie { get; set; } 886 | } 887 | 888 | public class SubscribersItem 889 | { 890 | 891 | public string defaultAvatar; 892 | 893 | public int province; 894 | 895 | public int authStatus; 896 | 897 | public string followed; 898 | 899 | public string avatarUrl; 900 | 901 | public int accountStatus; 902 | 903 | public int gender; 904 | 905 | public int city; 906 | 907 | public int birthday; 908 | 909 | public long userId; 910 | 911 | public int userType; 912 | 913 | public string nickname; 914 | 915 | public string signature; 916 | 917 | public string description; 918 | 919 | public string detailDescription; 920 | 921 | public int avatarImgId; 922 | 923 | public int backgroundImgId; 924 | 925 | public string backgroundUrl; 926 | 927 | public int authority; 928 | 929 | public string mutual; 930 | 931 | public string expertTags; 932 | 933 | public string experts; 934 | 935 | public int djStatus; 936 | 937 | public int vipType; 938 | 939 | public string remarkName; 940 | 941 | public int authenticationTypes; 942 | 943 | public string avatarDetail; 944 | 945 | public string avatarImgIdStr; 946 | 947 | public string backgroundImgIdStr; 948 | 949 | public string anchor; 950 | 951 | public string avatarImgId_str; 952 | } 953 | 954 | public class AvatarDetail 955 | { 956 | 957 | public int userType; 958 | 959 | public int identityLevel; 960 | 961 | public string identityIconUrl; 962 | } 963 | 964 | public class Creators 965 | { 966 | 967 | public string defaultAvatar; 968 | 969 | public int province; 970 | 971 | public int authStatus; 972 | 973 | public string followed; 974 | 975 | public string avatarUrl; 976 | 977 | public int accountStatus; 978 | 979 | public int gender; 980 | 981 | public int city; 982 | 983 | public int birthday; 984 | 985 | public long userId; 986 | 987 | public int userType; 988 | 989 | public string nickname; 990 | 991 | public string signature; 992 | 993 | public string description; 994 | 995 | public string detailDescription; 996 | 997 | public int avatarImgId; 998 | 999 | public int backgroundImgId; 1000 | 1001 | public string backgroundUrl; 1002 | 1003 | public int authority; 1004 | 1005 | public string mutual; 1006 | 1007 | public string expertTags; 1008 | 1009 | public string experts; 1010 | 1011 | public int djStatus; 1012 | 1013 | public int vipType; 1014 | 1015 | public string remarkName; 1016 | 1017 | public int authenticationTypes; 1018 | 1019 | public AvatarDetail avatarDetail; 1020 | 1021 | public string avatarImgIdStr; 1022 | 1023 | public string backgroundImgIdStr; 1024 | 1025 | public string anchor; 1026 | 1027 | public string avatarImgId_str; 1028 | } 1029 | 1030 | public class Sq 1031 | { 1032 | 1033 | public int br; 1034 | 1035 | public int fid; 1036 | 1037 | public int size; 1038 | 1039 | public int vd; 1040 | 1041 | public int sr; 1042 | } 1043 | 1044 | public class TracksItem 1045 | { 1046 | 1047 | public string name; 1048 | 1049 | public int id; 1050 | 1051 | public int pst; 1052 | 1053 | public int t; 1054 | 1055 | public List ar; 1056 | 1057 | public List alia; 1058 | 1059 | public int pop; 1060 | 1061 | public int st; 1062 | 1063 | public string rt; 1064 | 1065 | public int fee; 1066 | 1067 | public int v; 1068 | 1069 | public string crbt; 1070 | 1071 | public string cf; 1072 | 1073 | public Al al; 1074 | 1075 | public int dt; 1076 | 1077 | public H h; 1078 | 1079 | public M m; 1080 | 1081 | public L l; 1082 | 1083 | public Sq sq; 1084 | 1085 | public string hr; 1086 | 1087 | public string a; 1088 | 1089 | public string cd; 1090 | 1091 | public int no; 1092 | 1093 | public string rtUrl; 1094 | 1095 | public int ftype; 1096 | 1097 | public List rtUrls; 1098 | 1099 | public int djId; 1100 | 1101 | public int copyright; 1102 | 1103 | public int s_id; 1104 | 1105 | public int mark; 1106 | 1107 | public int originCoverType; 1108 | 1109 | 1110 | public string originSongSimpleData; 1111 | 1112 | public string tagPicList; 1113 | 1114 | public bool resourceState; 1115 | 1116 | public int version; 1117 | 1118 | public string songJumpInfo; 1119 | 1120 | public string entertainmentTags; 1121 | 1122 | public int single; 1123 | 1124 | public string noCopyrightRcmd; 1125 | 1126 | public string rurl; 1127 | 1128 | public int mst; 1129 | 1130 | public int cp; 1131 | 1132 | public int mv; 1133 | 1134 | public int rtype; 1135 | 1136 | public long publishTime; 1137 | } 1138 | 1139 | public class TrackIdsItem 1140 | { 1141 | 1142 | public int id; 1143 | 1144 | public int v; 1145 | 1146 | public int t; 1147 | 1148 | public int at; 1149 | 1150 | public string alg; 1151 | 1152 | public int uid; 1153 | 1154 | public string rcmdReason; 1155 | 1156 | public string sc; 1157 | 1158 | public string f; 1159 | 1160 | public string sr; 1161 | } 1162 | 1163 | public class Playlist 1164 | { 1165 | 1166 | public long id; 1167 | 1168 | public string name { get; set; } 1169 | 1170 | public long coverImgId; 1171 | 1172 | public string coverImgUrl { get; set; } 1173 | 1174 | public string coverImgId_str; 1175 | 1176 | public int adType; 1177 | 1178 | public long userId; 1179 | 1180 | public int createTime; 1181 | 1182 | public int status; 1183 | 1184 | public bool opRecommend; 1185 | 1186 | public bool highQuality; 1187 | 1188 | public bool newImported; 1189 | 1190 | public int updateTime; 1191 | 1192 | public int trackCount { get; set; } 1193 | 1194 | public int specialType; 1195 | 1196 | public int privacy; 1197 | 1198 | public int trackUpdateTime; 1199 | 1200 | public string commentThreadId; 1201 | 1202 | public int playCount; 1203 | 1204 | public long trackNumberUpdateTime; 1205 | 1206 | public int subscribedCount; 1207 | 1208 | public int cloudTrackCount; 1209 | 1210 | public bool ordered; 1211 | 1212 | public string description; 1213 | 1214 | public List tags; 1215 | 1216 | public string updateFrequency; 1217 | 1218 | public int backgroundCoverId; 1219 | 1220 | public string backgroundCoverUrl; 1221 | 1222 | public int titleImage; 1223 | 1224 | public string titleImageUrl; 1225 | 1226 | public string englishTitle; 1227 | 1228 | public string officialPlaylistType; 1229 | 1230 | public bool copied; 1231 | 1232 | public string relateResType; 1233 | 1234 | public List subscribers; 1235 | 1236 | public bool subscribed; 1237 | 1238 | public Creators creator; 1239 | 1240 | public List tracks; 1241 | 1242 | public string videoIds; 1243 | 1244 | public string videos; 1245 | 1246 | public List trackIds; 1247 | 1248 | public string bannedTrackIds; 1249 | 1250 | public string mvResourceInfos; 1251 | 1252 | public int shareCount; 1253 | 1254 | public int commentCount; 1255 | 1256 | public string remixVideo; 1257 | 1258 | public string sharedUsers; 1259 | 1260 | public string historySharedUsers; 1261 | 1262 | public string gradeStatus; 1263 | 1264 | public string score; 1265 | 1266 | public string algTags; 1267 | } 1268 | 1269 | public class FreeTrialPrivileges 1270 | { 1271 | 1272 | public string resConsumable; 1273 | 1274 | public string userConsumable; 1275 | 1276 | public string listenType; 1277 | } 1278 | 1279 | public class ChargeInfoListItems 1280 | { 1281 | 1282 | public int rate; 1283 | 1284 | public string chargeUrl; 1285 | 1286 | public string chargeMessage; 1287 | 1288 | public int chargeType; 1289 | } 1290 | 1291 | public class PrivilegesItems 1292 | { 1293 | 1294 | public int id; 1295 | 1296 | public int fee; 1297 | 1298 | public int payed; 1299 | 1300 | public int realPayed; 1301 | 1302 | public int st; 1303 | 1304 | public int pl; 1305 | 1306 | public int dl; 1307 | 1308 | public int sp; 1309 | 1310 | public int cp; 1311 | 1312 | public int subp; 1313 | 1314 | public string cs; 1315 | 1316 | public int maxbr; 1317 | 1318 | public int fl; 1319 | 1320 | public string pc; 1321 | 1322 | public string toast; 1323 | 1324 | public int flag; 1325 | 1326 | public string paidBigBang; 1327 | 1328 | public string preSell; 1329 | 1330 | public int playMaxbr; 1331 | 1332 | public int downloadMaxbr; 1333 | 1334 | public string maxBrLevel; 1335 | 1336 | public string playMaxBrLevel; 1337 | 1338 | public string downloadMaxBrLevel; 1339 | 1340 | public string plLevel; 1341 | 1342 | public string dlLevel; 1343 | 1344 | public string flLevel; 1345 | 1346 | public string rscl; 1347 | 1348 | public FreeTrialPrivileges freeTrialPrivilege; 1349 | 1350 | public List chargeInfoList; 1351 | } 1352 | 1353 | public class GedanDetail 1354 | { 1355 | 1356 | public long code; 1357 | 1358 | public string relatedVideos; 1359 | 1360 | public Playlist playlist { get; set; } 1361 | 1362 | public string urls; 1363 | 1364 | public List privileges; 1365 | 1366 | public string sharedPrivilege; 1367 | 1368 | public string resEntrance; 1369 | 1370 | public string fromUsers; 1371 | 1372 | public int fromUserCount; 1373 | 1374 | public string songFromUsers; 1375 | } 1376 | 1377 | public class MusicCheck 1378 | { 1379 | 1380 | public bool success{ get; set; } 1381 | 1382 | public string message{ get; set; } 1383 | } 1384 | 1385 | public class ArItem1 1386 | { 1387 | 1388 | public int id; 1389 | 1390 | public string name; 1391 | 1392 | public List tns; 1393 | 1394 | public List @alias; 1395 | } 1396 | 1397 | public class Al1 1398 | { 1399 | 1400 | public int id; 1401 | 1402 | public string name; 1403 | 1404 | public string picUrl { get; set; } 1405 | 1406 | public List tns; 1407 | 1408 | public string pic_str; 1409 | 1410 | public long pic; 1411 | } 1412 | 1413 | public class H1 1414 | { 1415 | 1416 | public int br; 1417 | 1418 | public int fid; 1419 | 1420 | public int size; 1421 | 1422 | public int vd; 1423 | 1424 | public int sr; 1425 | } 1426 | 1427 | public class M1 1428 | { 1429 | 1430 | public int br; 1431 | 1432 | public int fid; 1433 | 1434 | public int size; 1435 | 1436 | public int vd; 1437 | 1438 | public int sr; 1439 | } 1440 | 1441 | public class L1 1442 | { 1443 | 1444 | public int br; 1445 | 1446 | public int fid; 1447 | 1448 | public int size; 1449 | 1450 | public int vd; 1451 | 1452 | public int sr; 1453 | } 1454 | 1455 | public class Sq1 1456 | { 1457 | 1458 | public int br; 1459 | 1460 | public int fid; 1461 | 1462 | public int size; 1463 | 1464 | public int vd; 1465 | 1466 | public int sr; 1467 | } 1468 | 1469 | public class SongsItem1 1470 | { 1471 | 1472 | public string name { get; set; } 1473 | 1474 | public long id; 1475 | 1476 | public int pst; 1477 | 1478 | public int t; 1479 | 1480 | public List ar; 1481 | 1482 | public List alia; 1483 | 1484 | public int pop; 1485 | 1486 | public int st; 1487 | 1488 | public string rt; 1489 | 1490 | public int fee; 1491 | 1492 | public int v; 1493 | 1494 | public string crbt; 1495 | 1496 | public string cf; 1497 | 1498 | public Al1 al { get; set; } 1499 | 1500 | public int dt; 1501 | 1502 | public H1 h; 1503 | 1504 | public M1 m; 1505 | 1506 | public L1 l; 1507 | 1508 | public Sq1 sq; 1509 | 1510 | 1511 | public string hr; 1512 | 1513 | public string a; 1514 | 1515 | public string cd; 1516 | 1517 | public int no; 1518 | 1519 | public string rtUrl; 1520 | 1521 | public int ftype; 1522 | 1523 | public List rtUrls; 1524 | 1525 | public long djId; 1526 | 1527 | public long copyright; 1528 | 1529 | public long s_id; 1530 | 1531 | public long mark; 1532 | 1533 | public int originCoverType; 1534 | 1535 | public string originSongSimpleData; 1536 | 1537 | public string tagPicList; 1538 | 1539 | public string resourceState; 1540 | 1541 | public int version; 1542 | 1543 | public string songJumpInfo; 1544 | 1545 | public string entertainmentTags; 1546 | 1547 | public string awardTags; 1548 | 1549 | public int single; 1550 | 1551 | public string noCopyrightRcmd; 1552 | 1553 | public int mv; 1554 | 1555 | public int mst; 1556 | 1557 | public int cp; 1558 | 1559 | public int rtype; 1560 | 1561 | public string rurl; 1562 | 1563 | public long publishTime; 1564 | } 1565 | 1566 | public class FreeTrialPrivilege1 1567 | { 1568 | 1569 | public string resConsumable; 1570 | 1571 | public string userConsumable; 1572 | 1573 | public string listenType; 1574 | } 1575 | 1576 | public class ChargeInfoListItem1 1577 | { 1578 | 1579 | public long rate; 1580 | 1581 | public string chargeUrl; 1582 | 1583 | public string chargeMessage; 1584 | 1585 | public int chargeType; 1586 | } 1587 | 1588 | public class PrivilegesItem1 1589 | { 1590 | 1591 | public long id; 1592 | 1593 | public int fee; 1594 | 1595 | public int payed; 1596 | 1597 | public int st; 1598 | 1599 | public int pl; 1600 | 1601 | public int dl; 1602 | 1603 | public int sp; 1604 | 1605 | public int cp; 1606 | 1607 | public int subp; 1608 | 1609 | public string cs; 1610 | 1611 | public int maxbr; 1612 | 1613 | public int fl; 1614 | 1615 | public string toast; 1616 | 1617 | public int flag; 1618 | 1619 | public string preSell; 1620 | 1621 | public int playMaxbr; 1622 | 1623 | public int downloadMaxbr; 1624 | 1625 | public string maxBrLevel; 1626 | 1627 | public string playMaxBrLevel; 1628 | 1629 | public string downloadMaxBrLevel; 1630 | 1631 | public string plLevel; 1632 | 1633 | public string dlLevel; 1634 | 1635 | public string flLevel; 1636 | 1637 | public string rscl; 1638 | 1639 | public FreeTrialPrivilege1 freeTrialPrivilege; 1640 | 1641 | public List chargeInfoList; 1642 | } 1643 | 1644 | public class MusicDetail 1645 | { 1646 | 1647 | public List songs { get; set; } 1648 | 1649 | public List privileges; 1650 | 1651 | public int code; 1652 | } 1653 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TS3AudioBot-NetEaseCloudmusic-UNM 2 | TS3AudioBot-NetEaseCloudmusic-UnblockNeteaseMusic-plugin 3 | 4 | 支持Windows、Linux、Docker环境。 5 | 6 | ## 关于解锁版权歌曲 7 | 需要在自建的 [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)(推荐Docker版)里面的 app.js 中添加 `process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0`, 如果是docker版的话就在环境里面添加`NODE_TLS_REJECT_UNAUTHORIZED = 0`。 8 | 需要自建 [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server) 服务(推荐Docker版)。 9 | 10 | ## 关于设置文件YunSettings.ini 11 | `playMode=`是播放模式 12 | `WangYiYunAPI_Address=`是网易云API地址 13 | `cookies1=`是保存在你本地的身份验证,通过二维码登录获取。(不需要修改) 14 | `UNM_Address=`是 UnblockNeteaseMusic 服务的API地址。 15 | 16 | ## 替换插件文件后需要重启TS3AudioBot服务!!! 17 | 18 | ## 目前的指令: 19 | 正在播放的歌曲的图片和名称可以点机器人看它的头像和描述 20 | vip音乐想要先登陆才能播放完整版本:(输入指令后扫描机器人头像二维码登陆) 21 | `!yun login` 22 | 23 | 双击机器人,目前有以下指令 24 | 1.立即播放网易云音乐 25 | `!yun play 音乐名称` 或 `!yun play 音乐名称 歌手` (无版权歌曲点播) 26 | 27 | 2.播放网易云音乐歌单 28 | `!yun gedan 歌单id` 29 | 30 | 3.播放列表中的下一首 31 | `!yun next` 32 | 33 | 3.停止播放 34 | `!yun stop` 35 | 36 | 5.修改播放模式 37 | `!yun mode 数字0-3` 38 | `0 = 顺序播放` 39 | `1 = 顺序循环` 40 | `2 = 随机播放` 41 | `3 = 随机循环` 42 | 43 | 44 | ## 如果你需要基于主线[TS3AudioBot](https://github.com/Splamy/TS3AudioBot)构建Docker版的TS3AudioBot: 45 | Dockerfile支持x86、arm64、arm32三种架构,默认为x86。 如需其他架构请拉取Dockerfile修改注释 46 | 构建命令:`docker build -f Dockerfile -t local.docker.image/ts3audiobot:latest .` 47 | 运行方法参考[TS3AudioBot_docker](https://github.com/getdrunkonmovies-com/TS3AudioBot_docker)文档 48 | 49 | 相比主线解决了部分设备中关于ts3audiobot.db的权限无法运行的问题,Dockerfile中程序运行用户改为了root。 50 | 添加支持yt-dlp: 51 | 需要更改ts3audiobot.toml文件中 `youtube-dl = { path = "yt-dlp" }` 52 | 53 | 54 | 55 | ## 感谢 56 | 57 | - [Splamy](https://github.com/Splamy) 的 [TS3AudioBot](https://github.com/Splamy/TS3AudioBot) 项目 58 | - [bmatzelle](https://github.com/bmatzelle) 的 [Nini](https://github.com/bmatzelle/nini) 项目 59 | - [Fody](https://github.com/Fody) 的 [Costura.Fody](https://github.com/Fody/Costura/) 项目 60 | - [ZHANGTIANYAO1](https://github.com/ZHANGTIANYAO1) 的 [TS3AudioBot-NetEaseCloudmusic-plugin](https://github.com/ZHANGTIANYAO1/TS3AudioBot-NetEaseCloudmusic-plugin) 项目 61 | - [lauren12133](https://github.com/lauren12133) 关于TS3AudioBot编译Docker的教程和代码。 62 | -------------------------------------------------------------------------------- /YunPlugin-UNM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Reflection; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using System.Text.Json; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Nini.Config; 12 | using TS3AudioBot; 13 | using TS3AudioBot.Audio; 14 | using TS3AudioBot.CommandSystem; 15 | using TS3AudioBot.Plugins; 16 | using TSLib.Full; 17 | using NeteaseCloudMusicApi; 18 | 19 | public class YunPlugin : IBotPlugin 20 | { 21 | //===========================================初始化=========================================== 22 | static IConfigSource MyIni; 23 | PlayManager tempplayManager; 24 | InvokerData tempinvoker; 25 | Ts3Client tempts3Client; 26 | public static string cookies; 27 | public static int playMode; 28 | public static string WangYiYunAPI_Address; 29 | public static string UNM_Address; 30 | List playlist = new List(); 31 | public static int Playlocation = 0; 32 | private readonly SemaphoreSlim playlock = new SemaphoreSlim(1, 1); 33 | private readonly SemaphoreSlim Listeninglock = new SemaphoreSlim(1, 1); 34 | public void Initialize() 35 | { 36 | string iniFilePath; 37 | 38 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 39 | { 40 | Console.WriteLine("运行在Windows环境."); 41 | iniFilePath = "plugins/YunSettings.ini"; // Windows 文件目录 42 | } 43 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 44 | { 45 | string dockerEnvFilePath = "/.dockerenv"; 46 | 47 | if (File.Exists(dockerEnvFilePath)) 48 | { 49 | Console.WriteLine("运行在Docker环境."); 50 | } 51 | else 52 | { 53 | Console.WriteLine("运行在Linux环境."); 54 | } 55 | 56 | string location = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); 57 | iniFilePath = File.Exists(dockerEnvFilePath) ? location + "/data/plugins/YunSettings.ini" : location + "/plugins/YunSettings.ini"; 58 | } 59 | else 60 | { 61 | throw new NotSupportedException("不支持的操作系统"); 62 | } 63 | 64 | Console.WriteLine(iniFilePath); 65 | MyIni = new IniConfigSource(iniFilePath); 66 | 67 | playMode = int.TryParse(MyIni.Configs["YunBot"].Get("playMode"), out int playModeValue) ? playModeValue : 0; 68 | 69 | string cookiesValue = MyIni.Configs["YunBot"].Get("cookies1"); 70 | cookies = string.IsNullOrEmpty(cookiesValue) ? "" : cookiesValue; 71 | 72 | string wangYiYunAPI_AddressValue = MyIni.Configs["YunBot"].Get("WangYiYunAPI_Address"); 73 | WangYiYunAPI_Address = string.IsNullOrEmpty(wangYiYunAPI_AddressValue) ? "http://127.0.0.1:3000" : wangYiYunAPI_AddressValue; 74 | 75 | string unmAddressValue = MyIni.Configs["YunBot"].Get("UNM_Address"); 76 | UNM_Address = string.IsNullOrEmpty(unmAddressValue) ? "" : unmAddressValue; 77 | 78 | Console.WriteLine(playMode); 79 | Console.WriteLine(cookies); 80 | Console.WriteLine(WangYiYunAPI_Address); 81 | Console.WriteLine(UNM_Address); 82 | 83 | } 84 | 85 | 86 | 87 | public void SetPlplayManager(PlayManager playManager) 88 | { 89 | tempplayManager = playManager; 90 | } 91 | public PlayManager GetplayManager() 92 | { 93 | return tempplayManager; 94 | } 95 | 96 | public InvokerData Getinvoker() 97 | { 98 | return tempinvoker; 99 | } 100 | 101 | public void SetInvoker(InvokerData invoker) 102 | { 103 | tempinvoker = invoker; 104 | } 105 | 106 | public void SetTs3Client(Ts3Client ts3Client) 107 | { 108 | tempts3Client = ts3Client; 109 | } 110 | 111 | public Ts3Client GetTs3Client() 112 | { 113 | return tempts3Client; 114 | } 115 | 116 | //===========================================初始化=========================================== 117 | 118 | 119 | //===========================================播放模式=========================================== 120 | [Command("yun mode")] 121 | public string Playmode(int mode) 122 | { 123 | if (mode >= 0 && mode <= 3) 124 | { 125 | playMode = mode; 126 | MyIni.Configs["YunBot"].Set("playMode", mode.ToString()); 127 | MyIni.Save(); 128 | return mode switch 129 | { 130 | 0 => "顺序播放", 131 | 1 => "顺序循环", 132 | 2 => "随机播放", 133 | 3 => "随机循环", 134 | _ => "未知播放模式", 135 | }; 136 | } 137 | else 138 | { 139 | return "请输入正确的播放模式(0 到 3 之间的整数)"; 140 | } 141 | } 142 | //===========================================播放模式=========================================== 143 | 144 | 145 | //===========================================单曲播放=========================================== 146 | [Command("yun play")] 147 | public async Task CommandYunPlay(string arguments, PlayManager playManager, InvokerData invoker, Ts3Client ts3Client) 148 | { 149 | //playlist.Clear(); 150 | SetInvoker(invoker); 151 | SetPlplayManager(playManager); 152 | SetTs3Client(ts3Client); 153 | bool songFound = false; 154 | string urlSearch = $"{WangYiYunAPI_Address}/search?keywords={arguments}&limit=30"; 155 | string searchJson = await HttpGetAsync(urlSearch); 156 | yunSearchSong yunSearchSong = JsonSerializer.Deserialize(searchJson); 157 | string[] splitArguments = arguments.Split(" "); 158 | Console.WriteLine(splitArguments.Length); 159 | if (splitArguments.Length == 1) 160 | { 161 | _ = ProcessSong(yunSearchSong.result.songs[0].id, ts3Client, playManager, invoker); 162 | songFound = true; 163 | } 164 | else if (splitArguments.Length == 2) 165 | { 166 | // 歌曲名称和歌手 167 | string songName = splitArguments[0]; 168 | string artist = splitArguments[1]; 169 | 170 | for (int s = 0; s < yunSearchSong.result.songs.Count; s++) 171 | { 172 | if (yunSearchSong.result.songs[s].name == songName && yunSearchSong.result.songs[s].artists[0].name == artist) 173 | { 174 | _ = ProcessSong(yunSearchSong.result.songs[s].id, ts3Client, playManager, invoker); 175 | songFound = true; 176 | break; 177 | } 178 | } 179 | } 180 | else 181 | { 182 | // 输入为空或格式不符合预期 183 | Console.WriteLine("请输入有效的歌曲信息"); 184 | _ = ts3Client.SendChannelMessage("请输入有效的歌曲信息"); 185 | } 186 | Playlocation = songFound && Playlocation > 0 ? Playlocation - 1 : Playlocation; 187 | if (!songFound) 188 | { 189 | _ = ts3Client.SendChannelMessage("未找到歌曲"); 190 | } 191 | } 192 | 193 | //===========================================单曲播放=========================================== 194 | 195 | 196 | //===========================================歌单播放=========================================== 197 | [Command("yun gedan")] 198 | public async Task CommandYunGedan(string arguments, PlayManager playManager, InvokerData invoker, Ts3Client ts3Client, Player player) 199 | { 200 | playlist.Clear(); 201 | SetInvoker(invoker); 202 | SetPlplayManager(playManager); 203 | SetTs3Client(ts3Client); 204 | string urlSearch = $"{WangYiYunAPI_Address}/playlist/detail?id={arguments}"; 205 | string searchJson = await HttpGetAsync(urlSearch); 206 | GedanDetail gedanDetail = JsonSerializer.Deserialize(searchJson); 207 | string gedanshuliang = gedanDetail.playlist.trackCount.ToString(); 208 | _ = ts3Client.SendChannelMessage($"歌单共{gedanshuliang}首歌曲,正在添加到播放列表,请稍后。"); 209 | int loopCount = -1; 210 | for (int i = 0; i < gedanDetail.playlist.trackCount; i += 50) 211 | { 212 | Console.WriteLine($"查询循环次数{loopCount+1}"); 213 | loopCount += 1; 214 | if (i + 50 > gedanDetail.playlist.trackCount) 215 | { 216 | // 如果歌单的歌曲数量小于50,那么查询的数量就是歌曲的数量,否则查询的数量就是歌曲的数量减去50乘以查询的次数 217 | i = gedanDetail.playlist.trackCount < 50 ? gedanDetail.playlist.trackCount : gedanDetail.playlist.trackCount - 50 * loopCount; 218 | // 构建查询URL,如果歌单的歌曲数量小于50,那么偏移量就是0,否则偏移量就是查询的数量 219 | int offset = gedanDetail.playlist.trackCount < 50 ? 0 : i; 220 | urlSearch = $"{WangYiYunAPI_Address}/playlist/track/all?id={arguments}&limit=50&offset={offset}"; 221 | searchJson = await HttpGetAsync(urlSearch); 222 | GeDan geDan1 = JsonSerializer.Deserialize(searchJson); 223 | for (int j = 0; j < i; j++){ 224 | playlist.Add(geDan1.songs[j].id); 225 | Console.WriteLine(geDan1.songs[j].id); 226 | } 227 | break; 228 | } 229 | urlSearch = $"{WangYiYunAPI_Address}/playlist/track/all?id={arguments}&limit=50&offset={i}"; 230 | searchJson = await HttpGetAsync(urlSearch); 231 | GeDan geDan = JsonSerializer.Deserialize(searchJson); 232 | for (int j = 0; j < 50; j++){ 233 | playlist.Add(geDan.songs[j].id); 234 | Console.WriteLine(geDan.songs[j].id); 235 | } 236 | } 237 | Playlocation = 0; 238 | _ = ProcessSong(playlist[0], ts3Client, playManager, invoker); 239 | Console.WriteLine($"歌单共{playlist.Count}首歌"); 240 | await Listeninglock.WaitAsync(); 241 | playManager.ResourceStopped += async (sender, e) => await SongPlayMode(playManager, invoker, ts3Client); 242 | return $"播放列表加载完成,已加载{playlist.Count}首歌"; 243 | } 244 | //===========================================歌单播放=========================================== 245 | 246 | //===========================================下一曲=========================================== 247 | [Command("yun next")] 248 | public async Task CommandYunNext(PlayManager playManager, InvokerData invoker, Ts3Client ts3Client) 249 | { 250 | await SongPlayMode(playManager, invoker, ts3Client); 251 | } 252 | //===========================================下一曲============================================= 253 | [Command("yun stop")] 254 | public async Task CommandYunStop(PlayManager playManager, Ts3Client ts3Client) 255 | { 256 | playlist.Clear(); 257 | await playManager.Stop(); 258 | } 259 | 260 | //===========================================播放逻辑=========================================== 261 | private async Task SongPlayMode(PlayManager playManager, InvokerData invoker, Ts3Client ts3Client) 262 | { 263 | try 264 | { 265 | switch (playMode) 266 | { 267 | case 0: //顺序播放 268 | Playlocation += 1; 269 | await ProcessSong(playlist[Playlocation], ts3Client, playManager, invoker); 270 | break; 271 | case 1: //顺序循环 272 | if (Playlocation == playlist.Count - 1) 273 | { 274 | Playlocation = 0; 275 | await ProcessSong(playlist[Playlocation], ts3Client, playManager, invoker); 276 | } 277 | else 278 | { 279 | Playlocation += 1; 280 | await ProcessSong(playlist[Playlocation], ts3Client, playManager, invoker); 281 | } 282 | break; 283 | case 2: //随机播放 284 | Random random = new Random(); 285 | Playlocation = random.Next(0, playlist.Count); 286 | await ProcessSong(playlist[Playlocation], ts3Client, playManager, invoker); 287 | break; 288 | case 3: //随机循环 289 | Random random1 = new Random(); 290 | Playlocation = random1.Next(0, playlist.Count); 291 | await ProcessSong(playlist[Playlocation], ts3Client, playManager, invoker); 292 | break; 293 | default: 294 | break; 295 | } 296 | } 297 | catch (Exception) 298 | { 299 | Console.WriteLine("播放列表已空"); 300 | _ = ts3Client.SendChannelMessage("已停止播放"); 301 | } 302 | } 303 | private async Task ProcessSong(long id, Ts3Client ts3Client, PlayManager playManager, InvokerData invoker) 304 | { 305 | await playlock.WaitAsync(); 306 | try { 307 | long musicId = id; 308 | string musicCheckUrl = $"{WangYiYunAPI_Address}/check/music?id={musicId}"; 309 | string searchMusicCheckJson = await HttpGetAsync(musicCheckUrl); 310 | MusicCheck musicCheckJson = JsonSerializer.Deserialize(searchMusicCheckJson); 311 | 312 | // 根据音乐检查结果获取音乐播放URL 313 | string musicUrl = musicCheckJson.success.ToString() == "False" ? await GetcheckMusicUrl(musicId, true) : await GetMusicUrl(musicId, true); 314 | 315 | // 构造获取音乐详情的URL 316 | string musicDetailUrl = $"{WangYiYunAPI_Address}/song/detail?ids={musicId}"; 317 | string musicDetailJson = await HttpGetAsync(musicDetailUrl); 318 | MusicDetail musicDetail = JsonSerializer.Deserialize(musicDetailJson); 319 | 320 | // 从音乐详情中获取音乐图片URL和音乐名称 321 | string musicImgUrl = musicDetail.songs[0].al.picUrl; 322 | string musicName = musicDetail.songs[0].name; 323 | Console.WriteLine($"歌曲id:{musicId},歌曲名称:{musicName},版权:{musicCheckJson.success}"); 324 | 325 | // 设置Bot的头像为音乐图片 326 | _ = MainCommands.CommandBotAvatarSet(ts3Client, musicImgUrl); 327 | 328 | // 设置Bot的描述为音乐名称 329 | _ = MainCommands.CommandBotDescriptionSet(ts3Client, musicName); 330 | 331 | // 在控制台输出音乐播放URL 332 | Console.WriteLine(musicUrl); 333 | 334 | // 如果音乐播放URL不是错误,则添加到播放列表并通知频道 335 | if (musicUrl != "error") 336 | { 337 | _ = MainCommands.CommandPlay(playManager, invoker, musicUrl); 338 | 339 | // 更新Bot的描述为当前播放的音乐名称 340 | _ = MainCommands.CommandBotDescriptionSet(ts3Client, musicName); 341 | 342 | // 发送消息到频道,通知正在播放的音乐 343 | if (playlist.Count == 0) 344 | { 345 | _ = ts3Client.SendChannelMessage($"正在播放:{musicName}"); 346 | } 347 | else 348 | { 349 | _ = ts3Client.SendChannelMessage($"正在播放第{Playlocation+1}首:{musicName}"); 350 | } 351 | } 352 | } 353 | finally 354 | { 355 | playlock.Release(); 356 | } 357 | } 358 | //===========================================播放逻辑=========================================== 359 | 360 | //===========================================登录部分=========================================== 361 | [Command("yun login")] 362 | public static async Task CommandLoginAsync(Ts3Client ts3Client, TsFullClient tsClient) 363 | { 364 | // 获取登录二维码的 key 365 | string key = await GetLoginKey(); 366 | 367 | // 生成登录二维码并获取二维码图片的 base64 字符串 368 | string base64String = await GetLoginQRImage(key); 369 | 370 | // 发送二维码图片到 TeamSpeak 服务器频道 371 | await ts3Client.SendChannelMessage("正在生成二维码"); 372 | await ts3Client.SendChannelMessage(base64String); 373 | 374 | // 将 base64 字符串转换为二进制图片数据,上传到 TeamSpeak 服务器作为头像 375 | await UploadQRImage(tsClient, base64String); 376 | 377 | // 设置 TeamSpeak 服务器的描述信息 378 | await ts3Client.ChangeDescription("请用网易云APP扫描二维码登陆"); 379 | 380 | int i = 0; 381 | long code; 382 | string result; 383 | 384 | while (true) 385 | { 386 | // 检查登录状态 387 | Status1 status = await CheckLoginStatus(key); 388 | 389 | code = status.code; 390 | cookies = status.cookie; 391 | i = i + 1; 392 | Thread.Sleep(1000); 393 | 394 | if (i == 120) 395 | { 396 | result = "登陆失败或者超时"; 397 | await ts3Client.SendChannelMessage("登陆失败或者超时"); 398 | break; 399 | } 400 | 401 | if (code == 803) 402 | { 403 | result = "登陆成功"; 404 | await ts3Client.SendChannelMessage("登陆成功"); 405 | break; 406 | } 407 | } 408 | 409 | // 登录完成后删除上传的头像 410 | _ = await tsClient.DeleteAvatar(); 411 | 412 | // 更新 cookies 到配置文件 413 | MyIni.Configs["YunBot"].Set("cookies1", $"\"{cookies}\""); 414 | MyIni.Save(); 415 | 416 | return result; 417 | } 418 | 419 | // 获取登录二维码的 key 420 | private static async Task GetLoginKey() 421 | { 422 | string url = WangYiYunAPI_Address + "/login/qr/key" + "?timestamp=" + GetTimeStamp(); 423 | string json = await HttpGetAsync(url); 424 | LoginKey loginKey = JsonSerializer.Deserialize(json); 425 | return loginKey.data.unikey; 426 | } 427 | 428 | // 生成登录二维码并获取二维码图片的 base64 字符串 429 | private static async Task GetLoginQRImage(string key) 430 | { 431 | string url = WangYiYunAPI_Address + $"/login/qr/create?key={key}&qrimg=true×tamp={GetTimeStamp()}"; 432 | string json = await HttpGetAsync(url); 433 | LoginImg loginImg = JsonSerializer.Deserialize(json); 434 | return loginImg.data.qrimg; 435 | } 436 | 437 | // 上传二维码图片到 TeamSpeak 服务器 438 | private static async Task UploadQRImage(TsFullClient tsClient, string base64String) 439 | { 440 | string[] img = base64String.Split(","); 441 | byte[] bytes = Convert.FromBase64String(img[1]); 442 | Stream stream = new MemoryStream(bytes); 443 | _ = await tsClient.UploadAvatar(stream); 444 | } 445 | 446 | // 检查登录状态 447 | private static async Task CheckLoginStatus(string key) 448 | { 449 | string url = WangYiYunAPI_Address + $"/login/qr/check?key={key}×tamp={GetTimeStamp()}"; 450 | string json = await HttpGetAsync(url); 451 | Status1 status = JsonSerializer.Deserialize(json); 452 | Console.WriteLine(json); 453 | return status; 454 | } 455 | //===============================================登录部分=============================================== 456 | 457 | 458 | //===============================================获取歌曲信息=============================================== 459 | //以下全是功能性函数 460 | public static async Task GetMusicUrl(long id, bool usingCookie = false) 461 | { 462 | return await GetMusicUrl(id.ToString(), usingCookie); 463 | } 464 | 465 | public static async Task GetMusicUrl(string id, bool usingCookie = false) 466 | { 467 | string url = $"{WangYiYunAPI_Address}/song/url?id={id}"; 468 | if (usingCookie && !string.IsNullOrEmpty(cookies)) 469 | { 470 | url += $"&cookie={cookies}"; 471 | } 472 | 473 | string musicUrlJson = await HttpGetAsync(url); 474 | musicURL musicUrl = JsonSerializer.Deserialize(musicUrlJson); 475 | 476 | if (musicUrl.code != 200) 477 | { 478 | // 处理错误情况,这里你可以根据实际情况进行适当的处理 479 | return string.Empty; 480 | } 481 | 482 | string mp3 = musicUrl.data[0].url; 483 | return mp3; 484 | } 485 | 486 | public static async Task GetcheckMusicUrl(long id, bool usingcookie = false) //获得无版权歌曲URL 487 | { 488 | string url; 489 | url = WangYiYunAPI_Address + "/song/url?id=" + id.ToString() + "&proxy=" + UNM_Address; 490 | string musicurljson = await HttpGetAsync(url); 491 | musicURL musicurl = JsonSerializer.Deserialize(musicurljson); 492 | string mp3 = musicurl.data[0].url.ToString(); 493 | string checkmp3 = mp3.Replace("http://music.163.com", UNM_Address); 494 | return checkmp3; 495 | } 496 | 497 | public static async Task GetMusicName(string arguments)//获得歌曲名称 498 | { 499 | string musicdetailurl = WangYiYunAPI_Address + "/song/detail?ids=" + arguments; 500 | string musicdetailjson = await HttpGetAsync(musicdetailurl); 501 | MusicDetail musicDetail = JsonSerializer.Deserialize(musicdetailjson); 502 | string musicname = musicDetail.songs[0].name; 503 | return musicname; 504 | } 505 | //===============================================获取歌曲信息=============================================== 506 | 507 | 508 | 509 | //===============================================HTTP相关=============================================== 510 | public static async Task HttpGetAsync(string url) 511 | { 512 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 513 | request.Method = "GET"; 514 | request.Accept = "text/html, application/xhtml+xml, */*"; 515 | request.ContentType = "application/json"; 516 | 517 | // 异步获取响应 518 | using HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync(); 519 | // 异步读取响应流 520 | using StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8); 521 | return await reader.ReadToEndAsync(); 522 | } 523 | 524 | public static string GetTimeStamp() //获得时间戳 525 | { 526 | TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0); 527 | return Convert.ToInt64(ts.TotalSeconds).ToString(); 528 | } 529 | //===============================================HTTP相关=============================================== 530 | public void Dispose() 531 | { 532 | 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /YunPlugin-UNM.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp3.1 4 | 5 | 6 | 7 | .\lib\TS3AudioBot.dll 8 | 9 | 10 | .\lib\TSLib.dll 11 | 12 | 13 | .\lib\Nini.dll 14 | 15 | 16 | 17 | 18 | all 19 | 20 | 21 | -------------------------------------------------------------------------------- /YunSettings.ini: -------------------------------------------------------------------------------- 1 | [YunBot] 2 | playMode=0 3 | WangYiYunAPI_Address= 4 | cookies1= 5 | UNM_Address= 6 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine 2 | #FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine-arm64v8 3 | #FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine-arm32v7 4 | 5 | # which version and flavour of the audiobot to use 6 | ARG TS3_AUDIOBOT_RELEASE="0.12.0" 7 | ARG TS3_AUDIOBOT_FLAVOUR="TS3AudioBot_dotnetcore3.1.zip" 8 | 9 | 10 | # install all pre-requisites, these will be needed always 11 | RUN apk add \ 12 | opus-dev \ 13 | yt-dlp \ 14 | youtube-dl \ 15 | ffmpeg 16 | 17 | # download and install the TS3AudioBot in the specified version and flavour 18 | RUN mkdir -p /app \ 19 | && cd /app \ 20 | && echo "downloading https://github.com/Splamy/TS3AudioBot/releases/download/${TS3_AUDIOBOT_RELEASE}/${TS3_AUDIOBOT_FLAVOUR}" \ 21 | && wget https://github.com/Splamy/TS3AudioBot/releases/download/${TS3_AUDIOBOT_RELEASE}/${TS3_AUDIOBOT_FLAVOUR} -O TS3AudioBot.zip \ 22 | && unzip TS3AudioBot.zip \ 23 | && rm TS3AudioBot.zip 24 | 25 | # make data directory and chown it to the ts3bot user 26 | RUN mkdir -p /app/data 27 | 28 | # set the work dir to data, so users can properly mount their config files to this dir with -v /host/path/to/data:/data 29 | WORKDIR /app/data 30 | 31 | # expose the webserver port 32 | EXPOSE 58913 33 | 34 | CMD ["dotnet", "/app/TS3AudioBot.dll", "--non-interactive"] 35 | -------------------------------------------------------------------------------- /lib/Nini.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FiveHair/TS3AudioBot-NetEaseCloudmusic-plugin-UNM/1f4c013656a2254db79dd67b31c5fefb614c2e8a/lib/Nini.dll -------------------------------------------------------------------------------- /lib/TS3AudioBot.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FiveHair/TS3AudioBot-NetEaseCloudmusic-plugin-UNM/1f4c013656a2254db79dd67b31c5fefb614c2e8a/lib/TS3AudioBot.dll -------------------------------------------------------------------------------- /lib/TSLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FiveHair/TS3AudioBot-NetEaseCloudmusic-plugin-UNM/1f4c013656a2254db79dd67b31c5fefb614c2e8a/lib/TSLib.dll --------------------------------------------------------------------------------